mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-21 22:06:53 +08:00
426 lines
22 KiB
Python
426 lines
22 KiB
Python
#!/usr/bin/env python
|
|
|
|
import os, sys, shutil, tempfile, re
|
|
|
|
# Assume we are run from the galaxy root directory, add lib to the python path
|
|
cwd = os.getcwd()
|
|
new_path = [ os.path.join( cwd, "lib" ) ]
|
|
new_path.extend( sys.path[1:] )
|
|
sys.path = new_path
|
|
|
|
from galaxy import eggs
|
|
|
|
eggs.require( "nose" )
|
|
eggs.require( "NoseHTML" )
|
|
eggs.require( "NoseTestDiff" )
|
|
eggs.require( "twill==0.9" )
|
|
eggs.require( "Paste" )
|
|
eggs.require( "PasteDeploy" )
|
|
eggs.require( "Cheetah" )
|
|
|
|
# this should not be required, but it is under certain conditions, thanks to this bug:
|
|
# http://code.google.com/p/python-nose/issues/detail?id=284
|
|
eggs.require( "pysqlite" )
|
|
|
|
import atexit, logging, os, os.path, sys, tempfile
|
|
import twill, unittest, time
|
|
import subprocess, sys, threading, random
|
|
import httplib, socket
|
|
from paste import httpserver
|
|
import galaxy.app
|
|
from galaxy.app import UniverseApplication
|
|
from galaxy.web import buildapp
|
|
from galaxy import tools
|
|
from galaxy.util import bunch
|
|
from galaxy import util
|
|
from galaxy.util.json import to_json_string
|
|
|
|
import nose.core
|
|
import nose.config
|
|
import nose.loader
|
|
import nose.plugins.manager
|
|
|
|
log = logging.getLogger( "functional_tests.py" )
|
|
|
|
default_galaxy_test_host = "localhost"
|
|
default_galaxy_test_port_min = 8000
|
|
default_galaxy_test_port_max = 9999
|
|
default_galaxy_locales = 'en'
|
|
default_galaxy_test_file_dir = "test-data"
|
|
migrated_tool_panel_config = 'migrated_tools_conf.xml'
|
|
installed_tool_panel_configs = [ 'shed_tool_conf.xml' ]
|
|
|
|
def parse_tool_panel_config( config, shed_tools_dict ):
|
|
"""
|
|
Parse a shed-related tool panel config to generate the shed_tools_dict. This only happens when testing tools installed from the tool shed.
|
|
"""
|
|
last_galaxy_test_file_dir = None
|
|
last_tested_repository_name = None
|
|
last_tested_changeset_revision = None
|
|
tree = util.parse_xml( config )
|
|
root = tree.getroot()
|
|
for elem in root:
|
|
if elem.tag == 'tool':
|
|
galaxy_test_file_dir, \
|
|
last_tested_repository_name, \
|
|
last_tested_changeset_revision = get_installed_repository_info( elem,
|
|
last_galaxy_test_file_dir,
|
|
last_tested_repository_name,
|
|
last_tested_changeset_revision )
|
|
if galaxy_test_file_dir:
|
|
if galaxy_test_file_dir != last_galaxy_test_file_dir:
|
|
if not os.path.isabs( galaxy_test_file_dir ):
|
|
galaxy_test_file_dir = os.path.join( os.getcwd(), galaxy_test_file_dir )
|
|
guid = elem.get( 'guid' )
|
|
shed_tools_dict[ guid ] = galaxy_test_file_dir
|
|
last_galaxy_test_file_dir = galaxy_test_file_dir
|
|
elif elem.tag == 'section':
|
|
for section_elem in elem:
|
|
if section_elem.tag == 'tool':
|
|
galaxy_test_file_dir, \
|
|
last_tested_repository_name, \
|
|
last_tested_changeset_revision = get_installed_repository_info( section_elem,
|
|
last_galaxy_test_file_dir,
|
|
last_tested_repository_name,
|
|
last_tested_changeset_revision )
|
|
if galaxy_test_file_dir:
|
|
if galaxy_test_file_dir != last_galaxy_test_file_dir:
|
|
if not os.path.isabs( galaxy_test_file_dir ):
|
|
galaxy_test_file_dir = os.path.join( os.getcwd(), galaxy_test_file_dir )
|
|
guid = section_elem.get( 'guid' )
|
|
shed_tools_dict[ guid ] = galaxy_test_file_dir
|
|
last_galaxy_test_file_dir = galaxy_test_file_dir
|
|
return shed_tools_dict
|
|
|
|
def get_installed_repository_info( elem, last_galaxy_test_file_dir, last_tested_repository_name, last_tested_changeset_revision ):
|
|
"""
|
|
Return the GALAXY_TEST_FILE_DIR, the containing repository name and the change set revision for the tool elem.
|
|
This only happens when testing tools installed from the tool shed.
|
|
"""
|
|
tool_config_path = elem.get( 'file' )
|
|
installed_tool_path_items = tool_config_path.split( '/repos/' )
|
|
sans_shed = installed_tool_path_items[ 1 ]
|
|
path_items = sans_shed.split( '/' )
|
|
repository_owner = path_items[ 0 ]
|
|
repository_name = path_items[ 1 ]
|
|
changeset_revision = path_items[ 2 ]
|
|
if repository_name != last_tested_repository_name or changeset_revision != last_tested_changeset_revision:
|
|
# Locate the test-data directory.
|
|
installed_tool_path = os.path.join( installed_tool_path_items[ 0 ], 'repos', repository_owner, repository_name, changeset_revision )
|
|
for root, dirs, files in os.walk( installed_tool_path ):
|
|
if 'test-data' in dirs:
|
|
return os.path.join( root, 'test-data' ), repository_name, changeset_revision
|
|
return None, repository_name, changeset_revision
|
|
return last_galaxy_test_file_dir, last_tested_repository_name, last_tested_changeset_revision
|
|
|
|
def run_tests( test_config ):
|
|
loader = nose.loader.TestLoader( config=test_config )
|
|
plug_loader = test_config.plugins.prepareTestLoader( loader )
|
|
if plug_loader is not None:
|
|
loader = plug_loader
|
|
tests = loader.loadTestsFromNames( test_config.testNames )
|
|
test_runner = nose.core.TextTestRunner( stream=test_config.stream,
|
|
verbosity=test_config.verbosity,
|
|
config=test_config )
|
|
plug_runner = test_config.plugins.prepareTestRunner( test_runner )
|
|
if plug_runner is not None:
|
|
test_runner = plug_runner
|
|
return test_runner.run( tests )
|
|
|
|
def main():
|
|
# ---- Configuration ------------------------------------------------------
|
|
galaxy_test_host = os.environ.get( 'GALAXY_TEST_HOST', default_galaxy_test_host )
|
|
galaxy_test_port = os.environ.get( 'GALAXY_TEST_PORT', None )
|
|
galaxy_test_save = os.environ.get( 'GALAXY_TEST_SAVE', None)
|
|
tool_path = os.environ.get( 'GALAXY_TEST_TOOL_PATH', 'tools' )
|
|
if 'HTTP_ACCEPT_LANGUAGE' not in os.environ:
|
|
os.environ[ 'HTTP_ACCEPT_LANGUAGE' ] = default_galaxy_locales
|
|
testing_migrated_tools = '-migrated' in sys.argv
|
|
testing_installed_tools = '-installed' in sys.argv
|
|
|
|
if testing_migrated_tools or testing_installed_tools:
|
|
sys.argv.pop()
|
|
# Store a jsonified dictionary of tool_id : GALAXY_TEST_FILE_DIR pairs.
|
|
galaxy_tool_shed_test_file = 'shed_tools_dict'
|
|
# We need the upload tool for functional tests, so we'll create a temporary tool panel config that defines it.
|
|
fd, tmp_tool_panel_conf = tempfile.mkstemp()
|
|
os.write( fd, '<?xml version="1.0"?>\n' )
|
|
os.write( fd, '<toolbox>\n' )
|
|
os.write( fd, '<tool file="data_source/upload.xml"/>\n' )
|
|
os.write( fd, '</toolbox>\n' )
|
|
os.close( fd )
|
|
tool_config_file = tmp_tool_panel_conf
|
|
galaxy_test_file_dir = None
|
|
library_import_dir = None
|
|
user_library_import_dir = None
|
|
# Exclude all files except test_toolbox.py.
|
|
ignore_files = ( re.compile( r'^test_[adghlmsu]*' ), re.compile( r'^test_ta*' ) )
|
|
else:
|
|
tool_config_file = os.environ.get( 'GALAXY_TEST_TOOL_CONF', 'tool_conf.xml.sample' )
|
|
galaxy_test_file_dir = os.environ.get( 'GALAXY_TEST_FILE_DIR', default_galaxy_test_file_dir )
|
|
if not os.path.isabs( galaxy_test_file_dir ):
|
|
galaxy_test_file_dir = os.path.join( os.getcwd(), galaxy_test_file_dir )
|
|
library_import_dir = galaxy_test_file_dir
|
|
user_library_import_dir = os.path.join( galaxy_test_file_dir, 'users' )
|
|
ignore_files = ()
|
|
|
|
start_server = 'GALAXY_TEST_EXTERNAL' not in os.environ
|
|
if os.path.exists( 'tool_data_table_conf.test.xml' ):
|
|
tool_data_table_config_path = 'tool_data_table_conf.test.xml'
|
|
else:
|
|
tool_data_table_config_path = 'tool_data_table_conf.xml'
|
|
tool_dependency_dir = os.environ.get( 'GALAXY_TOOL_DEPENDENCY_DIR', None )
|
|
use_distributed_object_store = os.environ.get( 'GALAXY_USE_DISTRIBUTED_OBJECT_STORE', False )
|
|
|
|
if start_server:
|
|
psu_production = False
|
|
galaxy_test_proxy_port = None
|
|
if 'GALAXY_TEST_PSU_PRODUCTION' in os.environ:
|
|
if not galaxy_test_port:
|
|
raise Exception( 'Please set GALAXY_TEST_PORT to the port to which the proxy server will proxy' )
|
|
galaxy_test_proxy_port = os.environ.get( 'GALAXY_TEST_PROXY_PORT', None )
|
|
if not galaxy_test_proxy_port:
|
|
raise Exception( 'Please set GALAXY_TEST_PROXY_PORT to the port on which the proxy server is listening' )
|
|
base_file_path = os.environ.get( 'GALAXY_TEST_BASE_FILE_PATH', None )
|
|
if not base_file_path:
|
|
raise Exception( 'Please set GALAXY_TEST_BASE_FILE_PATH to the directory which will contain the dataset files directory' )
|
|
base_new_file_path = os.environ.get( 'GALAXY_TEST_BASE_NEW_FILE_PATH', None )
|
|
if not base_new_file_path:
|
|
raise Exception( 'Please set GALAXY_TEST_BASE_NEW_FILE_PATH to the directory which will contain the temporary directory' )
|
|
database_connection = os.environ.get( 'GALAXY_TEST_DBURI', None )
|
|
if not database_connection:
|
|
raise Exception( 'Please set GALAXY_TEST_DBURI to the URI of the database to be used for tests' )
|
|
nginx_upload_store = os.environ.get( 'GALAXY_TEST_NGINX_UPLOAD_STORE', None )
|
|
if not nginx_upload_store:
|
|
raise Exception( 'Please set GALAXY_TEST_NGINX_UPLOAD_STORE to the path where the nginx upload module places uploaded files' )
|
|
tool_config_file = 'tool_conf.xml.main'
|
|
default_cluster_job_runner = os.environ.get( 'GALAXY_TEST_DEFAULT_CLUSTER_JOB_RUNNER', 'pbs:///' )
|
|
file_path = tempfile.mkdtemp( dir=base_file_path )
|
|
new_file_path = tempfile.mkdtemp( dir=base_new_file_path )
|
|
cluster_files_directory = os.path.join( new_file_path, 'pbs' )
|
|
job_working_directory = os.path.join( new_file_path, 'job_working_directory' )
|
|
os.mkdir( cluster_files_directory )
|
|
os.mkdir( job_working_directory )
|
|
kwargs = dict( database_engine_option_pool_size = '10',
|
|
database_engine_option_max_overflow = '20',
|
|
database_engine_option_strategy = 'threadlocal',
|
|
nginx_x_accel_redirect_base = '/_x_accel_redirect',
|
|
nginx_upload_store = nginx_upload_store,
|
|
nginx_upload_path = '/_upload',
|
|
allow_library_path_paste = 'True',
|
|
cluster_files_directory = cluster_files_directory,
|
|
job_working_directory = job_working_directory,
|
|
outputs_to_working_directory = 'True',
|
|
set_metadata_externally = 'True',
|
|
static_enabled = 'False',
|
|
debug = 'False',
|
|
track_jobs_in_database = 'True',
|
|
job_scheduler_policy = 'FIFO',
|
|
start_job_runners = 'pbs',
|
|
default_cluster_job_runner = default_cluster_job_runner )
|
|
psu_production = True
|
|
else:
|
|
if 'GALAXY_TEST_DBPATH' in os.environ:
|
|
db_path = os.environ['GALAXY_TEST_DBPATH']
|
|
else:
|
|
tempdir = tempfile.mkdtemp()
|
|
db_path = os.path.join( tempdir, 'database' )
|
|
file_path = os.path.join( db_path, 'files' )
|
|
new_file_path = os.path.join( db_path, 'tmp' )
|
|
if 'GALAXY_TEST_DBURI' in os.environ:
|
|
database_connection = os.environ['GALAXY_TEST_DBURI']
|
|
else:
|
|
database_connection = 'sqlite:///' + os.path.join( db_path, 'universe.sqlite' )
|
|
kwargs = {}
|
|
for dir in file_path, new_file_path:
|
|
try:
|
|
os.makedirs( dir )
|
|
except OSError:
|
|
pass
|
|
print "Database connection:", database_connection
|
|
# ---- Build Application --------------------------------------------------
|
|
app = None
|
|
if start_server:
|
|
global_conf = { '__file__' : 'universe_wsgi.ini.sample' }
|
|
if psu_production:
|
|
global_conf = None
|
|
if not database_connection.startswith( 'sqlite://' ):
|
|
kwargs[ 'database_engine_option_max_overflow' ] = '20'
|
|
if tool_dependency_dir is not None:
|
|
kwargs[ 'tool_dependency_dir' ] = tool_dependency_dir
|
|
if use_distributed_object_store:
|
|
kwargs[ 'object_store' ] = 'distributed'
|
|
kwargs[ 'distributed_object_store_config_file' ] = 'distributed_object_store_conf.xml.sample'
|
|
# Build the Universe Application
|
|
app = UniverseApplication( job_queue_workers = 5,
|
|
id_secret = 'changethisinproductiontoo',
|
|
template_path = "templates",
|
|
database_connection = database_connection,
|
|
database_engine_option_pool_size = '10',
|
|
file_path = file_path,
|
|
new_file_path = new_file_path,
|
|
tool_path = tool_path,
|
|
update_integrated_tool_panel = False,
|
|
tool_config_file = tool_config_file,
|
|
datatype_converters_config_file = "datatype_converters_conf.xml.sample",
|
|
tool_parse_help = False,
|
|
test_conf = "test.conf",
|
|
tool_data_table_config_path = tool_data_table_config_path,
|
|
log_destination = "stdout",
|
|
use_heartbeat = False,
|
|
allow_user_creation = True,
|
|
allow_user_deletion = True,
|
|
admin_users = 'test@bx.psu.edu',
|
|
allow_library_path_paste = True,
|
|
library_import_dir = library_import_dir,
|
|
user_library_import_dir = user_library_import_dir,
|
|
global_conf = global_conf,
|
|
running_functional_tests=True,
|
|
**kwargs )
|
|
log.info( "Embedded Universe application started" )
|
|
# ---- Run webserver ------------------------------------------------------
|
|
server = None
|
|
|
|
if start_server:
|
|
webapp = buildapp.app_factory( dict(), use_translogger=False, static_enabled=False, app=app )
|
|
if galaxy_test_port is not None:
|
|
server = httpserver.serve( webapp, host=galaxy_test_host, port=galaxy_test_port, start_loop=False )
|
|
else:
|
|
random.seed()
|
|
for i in range( 0, 9 ):
|
|
try:
|
|
galaxy_test_port = str( random.randint( default_galaxy_test_port_min, default_galaxy_test_port_max ) )
|
|
log.debug( "Attempting to serve app on randomly chosen port: %s" % galaxy_test_port )
|
|
server = httpserver.serve( webapp, host=galaxy_test_host, port=galaxy_test_port, start_loop=False )
|
|
break
|
|
except socket.error, e:
|
|
if e[0] == 98:
|
|
continue
|
|
raise
|
|
else:
|
|
raise Exception( "Unable to open a port between %s and %s to start Galaxy server" % ( default_galaxy_test_port_min, default_galaxy_test_port_max ) )
|
|
if galaxy_test_proxy_port:
|
|
os.environ['GALAXY_TEST_PORT'] = galaxy_test_proxy_port
|
|
else:
|
|
os.environ['GALAXY_TEST_PORT'] = galaxy_test_port
|
|
|
|
t = threading.Thread( target=server.serve_forever )
|
|
t.start()
|
|
# Test if the server is up
|
|
for i in range( 10 ):
|
|
conn = httplib.HTTPConnection( galaxy_test_host, galaxy_test_port ) # directly test the app, not the proxy
|
|
conn.request( "GET", "/" )
|
|
if conn.getresponse().status == 200:
|
|
break
|
|
time.sleep( 0.1 )
|
|
else:
|
|
raise Exception( "Test HTTP server did not return '200 OK' after 10 tries" )
|
|
# Test if the proxy server is up
|
|
if psu_production:
|
|
conn = httplib.HTTPConnection( galaxy_test_host, galaxy_test_proxy_port ) # directly test the app, not the proxy
|
|
conn.request( "GET", "/" )
|
|
if not conn.getresponse().status == 200:
|
|
raise Exception( "Test HTTP proxy server did not return '200 OK'" )
|
|
log.info( "Embedded web server started" )
|
|
# ---- Load toolbox for generated tests -----------------------------------
|
|
# We don't add the tests to the path until everything is up and running
|
|
new_path = [ os.path.join( cwd, "test" ) ]
|
|
new_path.extend( sys.path[1:] )
|
|
sys.path = new_path
|
|
import functional.test_toolbox
|
|
# ---- Find tests ---------------------------------------------------------
|
|
if galaxy_test_proxy_port:
|
|
log.info( "Functional tests will be run against %s:%s" % ( galaxy_test_host, galaxy_test_proxy_port ) )
|
|
else:
|
|
log.info( "Functional tests will be run against %s:%s" % ( galaxy_test_host, galaxy_test_port ) )
|
|
success = False
|
|
try:
|
|
# What requires these? Handy for (eg) functional tests to save outputs?
|
|
if galaxy_test_save:
|
|
os.environ[ 'GALAXY_TEST_SAVE' ] = galaxy_test_save
|
|
# Pass in through script setenv, will leave a copy of ALL test validate files
|
|
os.environ[ 'GALAXY_TEST_HOST' ] = galaxy_test_host
|
|
if testing_migrated_tools or testing_installed_tools:
|
|
shed_tools_dict = {}
|
|
if testing_migrated_tools:
|
|
shed_tools_dict = parse_tool_panel_config( migrated_tool_panel_config, shed_tools_dict )
|
|
elif testing_installed_tools:
|
|
for shed_tool_config in installed_tool_panel_configs:
|
|
shed_tools_dict = parse_tool_panel_config( shed_tool_config, shed_tools_dict )
|
|
# Persist the shed_tools_dict to the galaxy_tool_shed_test_file.
|
|
shed_tools_file = open( galaxy_tool_shed_test_file, 'w' )
|
|
shed_tools_file.write( to_json_string( shed_tools_dict ) )
|
|
shed_tools_file.close()
|
|
if not os.path.isabs( galaxy_tool_shed_test_file ):
|
|
galaxy_tool_shed_test_file = os.path.join( os.getcwd(), galaxy_tool_shed_test_file )
|
|
os.environ[ 'GALAXY_TOOL_SHED_TEST_FILE' ] = galaxy_tool_shed_test_file
|
|
if testing_installed_tools:
|
|
# Eliminate the migrated_tool_panel_config from the app's tool_configs, append the list of installed_tool_panel_configs,
|
|
# and reload the app's toolbox.
|
|
relative_migrated_tool_panel_config = os.path.join( app.config.root, migrated_tool_panel_config )
|
|
tool_configs = app.config.tool_configs
|
|
if relative_migrated_tool_panel_config in tool_configs:
|
|
tool_configs.remove( relative_migrated_tool_panel_config )
|
|
for installed_tool_panel_config in installed_tool_panel_configs:
|
|
tool_configs.append( installed_tool_panel_config )
|
|
app.toolbox = tools.ToolBox( tool_configs, app.config.tool_path, app )
|
|
functional.test_toolbox.toolbox = app.toolbox
|
|
functional.test_toolbox.build_tests( testing_shed_tools=True )
|
|
test_config = nose.config.Config( env=os.environ, ignoreFiles=ignore_files, plugins=nose.plugins.manager.DefaultPluginManager() )
|
|
test_config.configure( sys.argv )
|
|
result = run_tests( test_config )
|
|
success = result.wasSuccessful()
|
|
try:
|
|
os.unlink( tmp_tool_panel_conf )
|
|
except:
|
|
log.info( "Unable to remove temporary file: %s" % tmp_tool_panel_conf )
|
|
try:
|
|
os.unlink( galaxy_tool_shed_test_file )
|
|
except:
|
|
log.info( "Unable to remove file: %s" % galaxy_tool_shed_test_file )
|
|
else:
|
|
functional.test_toolbox.toolbox = app.toolbox
|
|
functional.test_toolbox.build_tests()
|
|
if galaxy_test_file_dir:
|
|
os.environ[ 'GALAXY_TEST_FILE_DIR' ] = galaxy_test_file_dir
|
|
test_config = nose.config.Config( env=os.environ, ignoreFiles=ignore_files, plugins=nose.plugins.manager.DefaultPluginManager() )
|
|
test_config.configure( sys.argv )
|
|
result = run_tests( test_config )
|
|
success = result.wasSuccessful()
|
|
except:
|
|
log.exception( "Failure running tests" )
|
|
|
|
log.info( "Shutting down" )
|
|
# ---- Tear down -----------------------------------------------------------
|
|
if server:
|
|
log.info( "Shutting down embedded web server" )
|
|
server.server_close()
|
|
server = None
|
|
log.info( "Embedded web server stopped" )
|
|
if app:
|
|
log.info( "Shutting down app" )
|
|
app.shutdown()
|
|
app = None
|
|
log.info( "Embedded Universe application stopped" )
|
|
try:
|
|
if os.path.exists( tempdir ) and 'GALAXY_TEST_NO_CLEANUP' not in os.environ:
|
|
log.info( "Cleaning up temporary files in %s" % tempdir )
|
|
shutil.rmtree( tempdir )
|
|
except:
|
|
pass
|
|
if psu_production and 'GALAXY_TEST_NO_CLEANUP' not in os.environ:
|
|
for dir in ( file_path, new_file_path ):
|
|
try:
|
|
if os.path.exists( dir ):
|
|
log.info( 'Cleaning up temporary files in %s' % dir )
|
|
shutil.rmtree( dir )
|
|
except:
|
|
pass
|
|
if success:
|
|
return 0
|
|
else:
|
|
return 1
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit( main() )
|