PageRenderTime 74ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/test/base/twilltestcase.py

https://bitbucket.org/cistrome/cistrome-harvard/
Python | 2600 lines | 2584 code | 9 blank | 7 comment | 36 complexity | df943a8c086a1e8b21a50a6cb0a60990 MD5 | raw file
  1. import difflib
  2. import filecmp
  3. import logging
  4. import os
  5. import re
  6. import pprint
  7. import shutil
  8. import StringIO
  9. import subprocess
  10. import tarfile
  11. import tempfile
  12. import time
  13. import unittest
  14. import urllib
  15. import zipfile
  16. from galaxy.web import security
  17. from galaxy.web.framework.helpers import iff
  18. from galaxy.util.json import from_json_string
  19. from base.asserts import verify_assertions
  20. from galaxy import eggs
  21. eggs.require( "elementtree" )
  22. eggs.require( 'twill' )
  23. from elementtree import ElementTree
  24. import twill
  25. import twill.commands as tc
  26. from twill.other_packages._mechanize_dist import ClientForm
  27. #Force twill to log to a buffer -- FIXME: Should this go to stdout and be captured by nose?
  28. buffer = StringIO.StringIO()
  29. twill.set_output( buffer )
  30. tc.config( 'use_tidy', 0 )
  31. # Dial ClientCookie logging down (very noisy)
  32. logging.getLogger( "ClientCookie.cookies" ).setLevel( logging.WARNING )
  33. log = logging.getLogger( __name__ )
  34. class TwillTestCase( unittest.TestCase ):
  35. def setUp( self ):
  36. # Security helper
  37. self.security = security.SecurityHelper( id_secret='changethisinproductiontoo' )
  38. self.history_id = os.environ.get( 'GALAXY_TEST_HISTORY_ID', None )
  39. self.host = os.environ.get( 'GALAXY_TEST_HOST' )
  40. self.port = os.environ.get( 'GALAXY_TEST_PORT' )
  41. self.url = "http://%s:%s" % ( self.host, self.port )
  42. self.file_dir = os.environ.get( 'GALAXY_TEST_FILE_DIR', None )
  43. self.tool_shed_test_file = os.environ.get( 'GALAXY_TOOL_SHED_TEST_FILE', None )
  44. if self.tool_shed_test_file:
  45. f = open( self.tool_shed_test_file, 'r' )
  46. text = f.read()
  47. f.close()
  48. self.shed_tools_dict = from_json_string( text )
  49. else:
  50. self.shed_tools_dict = {}
  51. self.keepOutdir = os.environ.get( 'GALAXY_TEST_SAVE', '' )
  52. if self.keepOutdir > '':
  53. try:
  54. os.makedirs(self.keepOutdir)
  55. except:
  56. pass
  57. self.home()
  58. # Functions associated with files
  59. def files_diff( self, file1, file2, attributes=None ):
  60. """Checks the contents of 2 files for differences"""
  61. def get_lines_diff( diff ):
  62. count = 0
  63. for line in diff:
  64. if ( line.startswith( '+' ) and not line.startswith( '+++' ) ) or ( line.startswith( '-' ) and not line.startswith( '---' ) ):
  65. count += 1
  66. return count
  67. if not filecmp.cmp( file1, file2 ):
  68. files_differ = False
  69. local_file = open( file1, 'U' ).readlines()
  70. history_data = open( file2, 'U' ).readlines()
  71. if attributes is None:
  72. attributes = {}
  73. if attributes.get( 'sort', False ):
  74. history_data.sort()
  75. ##Why even bother with the check loop below, why not just use the diff output? This seems wasteful.
  76. if len( local_file ) == len( history_data ):
  77. for i in range( len( history_data ) ):
  78. if local_file[i].rstrip( '\r\n' ) != history_data[i].rstrip( '\r\n' ):
  79. files_differ = True
  80. break
  81. else:
  82. files_differ = True
  83. if files_differ:
  84. allowed_diff_count = int(attributes.get( 'lines_diff', 0 ))
  85. diff = list( difflib.unified_diff( local_file, history_data, "local_file", "history_data" ) )
  86. diff_lines = get_lines_diff( diff )
  87. if diff_lines > allowed_diff_count:
  88. if len(diff) < 60:
  89. diff_slice = diff[0:40]
  90. else:
  91. diff_slice = diff[:25] + ["********\n", "*SNIP *\n", "********\n"] + diff[-25:]
  92. #FIXME: This pdf stuff is rather special cased and has not been updated to consider lines_diff
  93. #due to unknown desired behavior when used in conjunction with a non-zero lines_diff
  94. #PDF forgiveness can probably be handled better by not special casing by __extension__ here
  95. #and instead using lines_diff or a regular expression matching
  96. #or by creating and using a specialized pdf comparison function
  97. if file1.endswith( '.pdf' ) or file2.endswith( '.pdf' ):
  98. # PDF files contain creation dates, modification dates, ids and descriptions that change with each
  99. # new file, so we need to handle these differences. As long as the rest of the PDF file does
  100. # not differ we're ok.
  101. valid_diff_strs = [ 'description', 'createdate', 'creationdate', 'moddate', 'id', 'producer', 'creator' ]
  102. valid_diff = False
  103. invalid_diff_lines = 0
  104. for line in diff_slice:
  105. # Make sure to lower case strings before checking.
  106. line = line.lower()
  107. # Diff lines will always start with a + or - character, but handle special cases: '--- local_file \n', '+++ history_data \n'
  108. if ( line.startswith( '+' ) or line.startswith( '-' ) ) and line.find( 'local_file' ) < 0 and line.find( 'history_data' ) < 0:
  109. for vdf in valid_diff_strs:
  110. if line.find( vdf ) < 0:
  111. valid_diff = False
  112. else:
  113. valid_diff = True
  114. # Stop checking as soon as we know we have a valid difference
  115. break
  116. if not valid_diff:
  117. invalid_diff_lines += 1
  118. log.info('## files diff on %s and %s lines_diff=%d, found diff = %d, found pdf invalid diff = %d' % (file1, file2, allowed_diff_count, diff_lines, invalid_diff_lines))
  119. if invalid_diff_lines > allowed_diff_count:
  120. # Print out diff_slice so we can see what failed
  121. print "###### diff_slice ######"
  122. raise AssertionError( "".join( diff_slice ) )
  123. else:
  124. log.info('## files diff on %s and %s lines_diff=%d, found diff = %d' % (file1, file2, allowed_diff_count, diff_lines))
  125. for line in diff_slice:
  126. for char in line:
  127. if ord( char ) > 128:
  128. raise AssertionError( "Binary data detected, not displaying diff" )
  129. raise AssertionError( "".join( diff_slice ) )
  130. def files_re_match( self, file1, file2, attributes=None ):
  131. """Checks the contents of 2 files for differences using re.match"""
  132. local_file = open( file1, 'U' ).readlines() # regex file
  133. history_data = open( file2, 'U' ).readlines()
  134. assert len( local_file ) == len( history_data ), 'Data File and Regular Expression File contain a different number of lines (%s != %s)\nHistory Data (first 40 lines):\n%s' % ( len( local_file ), len( history_data ), ''.join( history_data[:40] ) )
  135. if attributes is None:
  136. attributes = {}
  137. if attributes.get( 'sort', False ):
  138. history_data.sort()
  139. lines_diff = int(attributes.get( 'lines_diff', 0 ))
  140. line_diff_count = 0
  141. diffs = []
  142. for i in range( len( history_data ) ):
  143. if not re.match( local_file[i].rstrip( '\r\n' ), history_data[i].rstrip( '\r\n' ) ):
  144. line_diff_count += 1
  145. diffs.append( 'Regular Expression: %s\nData file : %s' % ( local_file[i].rstrip( '\r\n' ), history_data[i].rstrip( '\r\n' ) ) )
  146. if line_diff_count > lines_diff:
  147. raise AssertionError( "Regular expression did not match data file (allowed variants=%i):\n%s" % ( lines_diff, "".join( diffs ) ) )
  148. def files_re_match_multiline( self, file1, file2, attributes=None ):
  149. """Checks the contents of 2 files for differences using re.match in multiline mode"""
  150. local_file = open( file1, 'U' ).read() # regex file
  151. if attributes is None:
  152. attributes = {}
  153. if attributes.get( 'sort', False ):
  154. history_data = open( file2, 'U' ).readlines()
  155. history_data.sort()
  156. history_data = ''.join( history_data )
  157. else:
  158. history_data = open( file2, 'U' ).read()
  159. #lines_diff not applicable to multiline matching
  160. assert re.match( local_file, history_data, re.MULTILINE ), "Multiline Regular expression did not match data file"
  161. def files_contains( self, file1, file2, attributes=None ):
  162. """Checks the contents of file2 for substrings found in file1, on a per-line basis"""
  163. local_file = open( file1, 'U' ).readlines() # regex file
  164. #TODO: allow forcing ordering of contains
  165. history_data = open( file2, 'U' ).read()
  166. lines_diff = int( attributes.get( 'lines_diff', 0 ) )
  167. line_diff_count = 0
  168. while local_file:
  169. contains = local_file.pop( 0 ).rstrip( '\n\r' )
  170. if contains not in history_data:
  171. line_diff_count += 1
  172. if line_diff_count > lines_diff:
  173. raise AssertionError( "Failed to find '%s' in history data. (lines_diff=%i):\n" % ( contains, lines_diff ) )
  174. def get_filename( self, filename, shed_tool_id=None ):
  175. if shed_tool_id and self.shed_tools_dict:
  176. file_dir = self.shed_tools_dict[ shed_tool_id ]
  177. if not file_dir:
  178. file_dir = self.file_dir
  179. else:
  180. file_dir = self.file_dir
  181. return os.path.abspath( os.path.join( file_dir, filename ) )
  182. def save_log( *path ):
  183. """Saves the log to a file"""
  184. filename = os.path.join( *path )
  185. file(filename, 'wt').write(buffer.getvalue())
  186. def upload_file( self, filename, ftype='auto', dbkey='unspecified (?)', space_to_tab=False, metadata=None, composite_data=None, name=None, shed_tool_id=None, wait=True ):
  187. """
  188. Uploads a file. If shed_tool_id has a value, we're testing tools migrated from the distribution to the tool shed,
  189. so the tool-data directory of test data files is contained in the installed tool shed repository.
  190. """
  191. self.visit_url( "%s/tool_runner?tool_id=upload1" % self.url )
  192. try:
  193. self.refresh_form( "file_type", ftype ) # Refresh, to support composite files
  194. tc.fv( "tool_form", "dbkey", dbkey )
  195. if metadata:
  196. for elem in metadata:
  197. tc.fv( "tool_form", "files_metadata|%s" % elem.get( 'name' ), elem.get( 'value' ) )
  198. if composite_data:
  199. for i, composite_file in enumerate( composite_data ):
  200. filename = self.get_filename( composite_file.get( 'value' ), shed_tool_id=shed_tool_id )
  201. tc.formfile( "tool_form", "files_%i|file_data" % i, filename )
  202. tc.fv( "tool_form", "files_%i|space_to_tab" % i, composite_file.get( 'space_to_tab', False ) )
  203. else:
  204. filename = self.get_filename( filename, shed_tool_id=shed_tool_id )
  205. tc.formfile( "tool_form", "file_data", filename )
  206. tc.fv( "tool_form", "space_to_tab", space_to_tab )
  207. if name:
  208. # NAME is a hidden form element, so the following prop must
  209. # set to use it.
  210. tc.config("readonly_controls_writeable", 1)
  211. tc.fv( "tool_form", "NAME", name )
  212. tc.submit( "runtool_btn" )
  213. self.home()
  214. except AssertionError, err:
  215. errmsg = "Uploading file resulted in the following exception. Make sure the file (%s) exists. " % filename
  216. errmsg += str( err )
  217. raise AssertionError( errmsg )
  218. if not wait:
  219. return
  220. # Make sure every history item has a valid hid
  221. hids = self.get_hids_in_history()
  222. for hid in hids:
  223. try:
  224. int( hid )
  225. except:
  226. raise AssertionError( "Invalid hid (%s) created when uploading file %s" % ( hid, filename ) )
  227. # Wait for upload processing to finish (TODO: this should be done in each test case instead)
  228. self.wait()
  229. def upload_url_paste( self, url_paste, ftype='auto', dbkey='unspecified (?)' ):
  230. """Pasted data in the upload utility"""
  231. self.visit_page( "tool_runner/index?tool_id=upload1" )
  232. try:
  233. self.refresh_form( "file_type", ftype ) #Refresh, to support composite files
  234. tc.fv( "tool_form", "dbkey", dbkey )
  235. tc.fv( "tool_form", "url_paste", url_paste )
  236. tc.submit( "runtool_btn" )
  237. self.home()
  238. except Exception, e:
  239. errmsg = "Problem executing upload utility using url_paste: %s" % str( e )
  240. raise AssertionError( errmsg )
  241. # Make sure every history item has a valid hid
  242. hids = self.get_hids_in_history()
  243. for hid in hids:
  244. try:
  245. int( hid )
  246. except:
  247. raise AssertionError( "Invalid hid (%s) created when pasting %s" % ( hid, url_paste ) )
  248. # Wait for upload processing to finish (TODO: this should be done in each test case instead)
  249. self.wait()
  250. def json_from_url( self, url ):
  251. self.visit_url( url )
  252. return from_json_string( self.last_page() )
  253. # Functions associated with histories
  254. def get_history_from_api( self, encoded_history_id=None ):
  255. if encoded_history_id is None:
  256. history = self.get_latest_history()
  257. encoded_history_id = history[ 'id' ]
  258. return self.json_from_url( '/api/histories/%s/contents' % encoded_history_id )
  259. def get_latest_history( self ):
  260. return self.json_from_url( '/api/histories' )[ 0 ]
  261. def find_hda_by_dataset_name( self, name, history=None ):
  262. if history is None:
  263. history = self.get_history_from_api()
  264. for hda in history:
  265. if hda[ 'name' ] == name:
  266. return hda
  267. def check_history_for_errors( self ):
  268. """Raises an exception if there are errors in a history"""
  269. self.home()
  270. self.visit_page( "history" )
  271. page = self.last_page()
  272. if page.find( 'error' ) > -1:
  273. raise AssertionError( 'Errors in the history for user %s' % self.user )
  274. def check_history_for_string( self, patt, show_deleted=False ):
  275. """Breaks patt on whitespace and searches for each element seperately in the history"""
  276. self.home()
  277. if show_deleted:
  278. self.visit_page( "history?show_deleted=True" )
  279. else:
  280. self.visit_page( "history" )
  281. for subpatt in patt.split():
  282. try:
  283. tc.find( subpatt )
  284. except:
  285. fname = self.write_temp_file( tc.browser.get_html() )
  286. errmsg = "no match to '%s'\npage content written to '%s'" % ( subpatt, fname )
  287. raise AssertionError( errmsg )
  288. self.home()
  289. def check_history_for_exact_string( self, string, show_deleted=False ):
  290. """Looks for exact match to 'string' in history page"""
  291. self.home()
  292. if show_deleted:
  293. self.visit_page( "history?show_deleted=True" )
  294. else:
  295. self.visit_page( "history" )
  296. try:
  297. tc.find( string )
  298. except:
  299. fname = self.write_temp_file( tc.browser.get_html() )
  300. errmsg = "no match to '%s'\npage content written to '%s'" % ( string, fname )
  301. raise AssertionError( errmsg )
  302. self.home()
  303. def check_history_json( self, pattern, check_fn, show_deleted=None, multiline=True ):
  304. """
  305. Tries to find a JSON string in the history page using the regex pattern,
  306. parse it, and assert check_fn returns True when called on that parsed
  307. data.
  308. """
  309. self.home()
  310. if show_deleted:
  311. self.visit_page( "history?show_deleted=True" )
  312. elif show_deleted == False:
  313. self.visit_page( "history?show_deleted=False" )
  314. else:
  315. self.visit_page( "history" )
  316. json_data = {}
  317. try:
  318. tc.find( pattern, flags=( 'm' if multiline else '' ) )
  319. # twill stores the regex match in a special stack variable
  320. match = twill.namespaces.get_twill_glocals()[1][ '__match__' ]
  321. json_data = from_json_string( match )
  322. assert check_fn( json_data ), 'failed check_fn: %s' % ( check_fn.func_name )
  323. except Exception, exc:
  324. log.error( exc, exc_info=True )
  325. log.debug( 'json_data: %s', ( '\n' + pprint.pformat( json_data ) if json_data else '(no match)' ) )
  326. fname = self.write_temp_file( tc.browser.get_html() )
  327. errmsg = ( "json '%s' could not be found or failed check_fn" % ( pattern ) +
  328. "\npage content written to '%s'" % ( fname ) )
  329. raise AssertionError( errmsg )
  330. self.home()
  331. def is_history_empty( self ):
  332. """
  333. Uses history page JSON to determine whether this history is empty
  334. (i.e. has no undeleted datasets).
  335. """
  336. return len( self.get_history_from_api() ) == 0
  337. def check_hda_json_for_key_value( self, hda_id, key, value, use_string_contains=False ):
  338. """
  339. Uses the history API to determine whether the current history:
  340. (1) Has a history dataset with the required ID.
  341. (2) That dataset has the required key.
  342. (3) The contents of that key match the provided value.
  343. If use_string_contains=True, this will perform a substring match, otherwise an exact match.
  344. """
  345. #TODO: multi key, value
  346. hda = dict()
  347. for history_item in self.get_history_from_api():
  348. if history_item[ 'id' ] == hda_id:
  349. hda = self.json_from_url( history_item[ 'url' ] )
  350. break
  351. if hda:
  352. if key in hda:
  353. if use_string_contains:
  354. return value in hda[ key ]
  355. else:
  356. return value == hda[ key ]
  357. return False
  358. def clear_history( self ):
  359. """Empties a history of all datasets"""
  360. self.visit_page( "clear_history" )
  361. self.check_history_for_string( 'Your history is empty' )
  362. self.home()
  363. def delete_history( self, id ):
  364. """Deletes one or more histories"""
  365. history_list = self.get_histories_as_data_list()
  366. self.assertTrue( history_list )
  367. num_deleted = len( id.split( ',' ) )
  368. self.home()
  369. self.visit_page( "history/list?operation=delete&id=%s" % ( id ) )
  370. check_str = 'Deleted %d %s' % ( num_deleted, iff( num_deleted != 1, "histories", "history" ) )
  371. self.check_page_for_string( check_str )
  372. self.home()
  373. def delete_current_history( self, strings_displayed=[] ):
  374. """Deletes the current history"""
  375. self.home()
  376. self.visit_page( "history/delete_current" )
  377. for check_str in strings_displayed:
  378. self.check_page_for_string( check_str )
  379. self.home()
  380. def get_histories_as_data_list( self ):
  381. """Returns the data elements of all histories"""
  382. tree = self.histories_as_xml_tree()
  383. data_list = [ elem for elem in tree.findall("data") ]
  384. return data_list
  385. def get_history_as_data_list( self, show_deleted=False ):
  386. """Returns the data elements of a history"""
  387. tree = self.history_as_xml_tree( show_deleted=show_deleted )
  388. data_list = [ elem for elem in tree.findall("data") ]
  389. return data_list
  390. def history_as_xml_tree( self, show_deleted=False ):
  391. """Returns a parsed xml object of a history"""
  392. self.home()
  393. self.visit_page( 'history?as_xml=True&show_deleted=%s' % show_deleted )
  394. xml = self.last_page()
  395. tree = ElementTree.fromstring(xml)
  396. return tree
  397. def histories_as_xml_tree( self ):
  398. """Returns a parsed xml object of all histories"""
  399. self.home()
  400. self.visit_page( 'history/list_as_xml' )
  401. xml = self.last_page()
  402. tree = ElementTree.fromstring(xml)
  403. return tree
  404. def history_options( self, user=False, active_datasets=False, activatable_datasets=False, histories_shared_by_others=False ):
  405. """Mimics user clicking on history options link"""
  406. self.home()
  407. self.visit_page( "root/history_options" )
  408. if user:
  409. self.check_page_for_string( 'Previously</a> stored histories' )
  410. if active_datasets:
  411. self.check_page_for_string( 'Create</a> a new empty history' )
  412. self.check_page_for_string( 'Construct workflow</a> from current history' )
  413. self.check_page_for_string( 'Copy</a> current history' )
  414. self.check_page_for_string( 'Share</a> current history' )
  415. self.check_page_for_string( 'Change default permissions</a> for current history' )
  416. if histories_shared_by_others:
  417. self.check_page_for_string( 'Histories</a> shared with you by others' )
  418. if activatable_datasets:
  419. self.check_page_for_string( 'Show deleted</a> datasets in current history' )
  420. self.check_page_for_string( 'Rename</a> current history' )
  421. self.check_page_for_string( 'Delete</a> current history' )
  422. self.home()
  423. def new_history( self, name=None ):
  424. """Creates a new, empty history"""
  425. self.home()
  426. if name:
  427. self.visit_url( "%s/history_new?name=%s" % ( self.url, name ) )
  428. else:
  429. self.visit_url( "%s/history_new" % self.url )
  430. self.check_history_for_string('Your history is empty')
  431. self.home()
  432. def rename_history( self, id, old_name, new_name ):
  433. """Rename an existing history"""
  434. self.home()
  435. self.visit_page( "history/rename?id=%s&name=%s" % ( id, new_name ) )
  436. check_str = 'History: %s renamed to: %s' % ( old_name, urllib.unquote( new_name ) )
  437. self.check_page_for_string( check_str )
  438. self.home()
  439. def set_history( self ):
  440. """Sets the history (stores the cookies for this run)"""
  441. if self.history_id:
  442. self.home()
  443. self.visit_page( "history?id=%s" % self.history_id )
  444. else:
  445. self.new_history()
  446. self.home()
  447. def share_current_history( self, email, strings_displayed=[], strings_displayed_after_submit=[],
  448. action='', action_strings_displayed=[], action_strings_displayed_after_submit=[] ):
  449. """Share the current history with different users"""
  450. self.visit_url( "%s/history/share" % self.url )
  451. for check_str in strings_displayed:
  452. self.check_page_for_string( check_str )
  453. tc.fv( 'share', 'email', email )
  454. tc.submit( 'share_button' )
  455. for check_str in strings_displayed_after_submit:
  456. self.check_page_for_string( check_str )
  457. if action:
  458. # If we have an action, then we are sharing datasets with users that do not have access permissions on them
  459. for check_str in action_strings_displayed:
  460. self.check_page_for_string( check_str )
  461. tc.fv( 'share_restricted', 'action', action )
  462. tc.submit( "share_restricted_button" )
  463. for check_str in action_strings_displayed_after_submit:
  464. self.check_page_for_string( check_str )
  465. self.home()
  466. def share_histories_with_users( self, ids, emails, strings_displayed=[], strings_displayed_after_submit=[],
  467. action=None, action_strings_displayed=[] ):
  468. """Share one or more histories with one or more different users"""
  469. self.visit_url( "%s/history/list?id=%s&operation=Share" % ( self.url, ids ) )
  470. for check_str in strings_displayed:
  471. self.check_page_for_string( check_str )
  472. tc.fv( 'share', 'email', emails )
  473. tc.submit( 'share_button' )
  474. for check_str in strings_displayed_after_submit:
  475. self.check_page_for_string( check_str )
  476. if action:
  477. # If we have an action, then we are sharing datasets with users that do not have access permissions on them
  478. tc.fv( 'share_restricted', 'action', action )
  479. tc.submit( "share_restricted_button" )
  480. for check_str in action_strings_displayed:
  481. self.check_page_for_string( check_str )
  482. self.home()
  483. def unshare_history( self, history_id, user_id, strings_displayed=[] ):
  484. """Unshare a history that has been shared with another user"""
  485. self.visit_url( "%s/history/list?id=%s&operation=share+or+publish" % ( self.url, history_id ) )
  486. for check_str in strings_displayed:
  487. self.check_page_for_string( check_str )
  488. self.visit_url( "%s/history/sharing?unshare_user=%s&id=%s" % ( self.url, user_id, history_id ) )
  489. self.home()
  490. def switch_history( self, id='', name='' ):
  491. """Switches to a history in the current list of histories"""
  492. self.visit_url( "%s/history/list?operation=switch&id=%s" % ( self.url, id ) )
  493. if name:
  494. self.check_history_for_exact_string( name )
  495. self.home()
  496. def view_stored_active_histories( self, strings_displayed=[] ):
  497. self.home()
  498. self.visit_page( "history/list" )
  499. self.check_page_for_string( 'Saved Histories' )
  500. self.check_page_for_string( 'operation=Rename' )
  501. self.check_page_for_string( 'operation=Switch' )
  502. self.check_page_for_string( 'operation=Delete' )
  503. for check_str in strings_displayed:
  504. self.check_page_for_string( check_str )
  505. self.home()
  506. def view_stored_deleted_histories( self, strings_displayed=[] ):
  507. self.home()
  508. self.visit_page( "history/list?f-deleted=True" )
  509. self.check_page_for_string( 'Saved Histories' )
  510. self.check_page_for_string( 'operation=Undelete' )
  511. for check_str in strings_displayed:
  512. self.check_page_for_string( check_str )
  513. self.home()
  514. def view_shared_histories( self, strings_displayed=[] ):
  515. self.home()
  516. self.visit_page( "history/list_shared" )
  517. for check_str in strings_displayed:
  518. self.check_page_for_string( check_str )
  519. self.home()
  520. def copy_history( self, history_id, copy_choice, strings_displayed=[], strings_displayed_after_submit=[] ):
  521. self.home()
  522. self.visit_page( "history/copy?id=%s" % history_id )
  523. for check_str in strings_displayed:
  524. self.check_page_for_string( check_str )
  525. tc.fv( '1', 'copy_choice', copy_choice )
  526. tc.submit( 'copy_choice_button' )
  527. for check_str in strings_displayed_after_submit:
  528. self.check_page_for_string( check_str )
  529. self.home()
  530. def make_accessible_via_link( self, history_id, strings_displayed=[], strings_displayed_after_submit=[] ):
  531. self.home()
  532. self.visit_page( "history/list?operation=share+or+publish&id=%s" % history_id )
  533. for check_str in strings_displayed:
  534. self.check_page_for_string( check_str )
  535. # twill barfs on this form, possibly because it contains no fields, but not sure.
  536. # In any case, we have to mimic the form submission
  537. self.home()
  538. self.visit_page( 'history/sharing?id=%s&make_accessible_via_link=True' % history_id )
  539. for check_str in strings_displayed_after_submit:
  540. self.check_page_for_string( check_str )
  541. self.home()
  542. def disable_access_via_link( self, history_id, strings_displayed=[], strings_displayed_after_submit=[] ):
  543. self.home()
  544. self.visit_page( "history/list?operation=share+or+publish&id=%s" % history_id )
  545. for check_str in strings_displayed:
  546. self.check_page_for_string( check_str )
  547. # twill barfs on this form, possibly because it contains no fields, but not sure.
  548. # In any case, we have to mimic the form submission
  549. self.home()
  550. self.visit_page( 'history/sharing?id=%s&disable_link_access=True' % history_id )
  551. for check_str in strings_displayed_after_submit:
  552. self.check_page_for_string( check_str )
  553. self.home()
  554. def import_history_via_url( self, history_id, email, strings_displayed_after_submit=[] ):
  555. self.home()
  556. self.visit_page( "history/imp?&id=%s" % history_id )
  557. for check_str in strings_displayed_after_submit:
  558. self.check_page_for_string( check_str )
  559. self.home()
  560. # Functions associated with datasets (history items) and meta data
  561. def _get_job_stream_output( self, hda_id, stream, format ):
  562. self.visit_page( "datasets/%s/%s" % ( self.security.encode_id( hda_id ), stream ) )
  563. output = self.last_page()
  564. return self._format_stream( output, stream, format )
  565. def _format_stream( self, output, stream, format ):
  566. if format:
  567. msg = "---------------------- >> begin tool %s << -----------------------\n" % stream
  568. msg += output + "\n"
  569. msg += "----------------------- >> end tool %s << ------------------------\n" % stream
  570. else:
  571. msg = output
  572. return msg
  573. def get_job_stdout( self, hda_id, format=False ):
  574. return self._get_job_stream_output( hda_id, 'stdout', format )
  575. def get_job_stderr( self, hda_id, format=False ):
  576. return self._get_job_stream_output( hda_id, 'stderr', format )
  577. def _assert_dataset_state( self, elem, state ):
  578. if elem.get( 'state' ) != state:
  579. errmsg = "Expecting dataset state '%s', but state is '%s'. Dataset blurb: %s\n\n" % ( state, elem.get('state'), elem.text.strip() )
  580. errmsg += self.get_job_stderr( elem.get( 'id' ), format=True )
  581. raise AssertionError( errmsg )
  582. def check_metadata_for_string( self, patt, hid=None ):
  583. """Looks for 'patt' in the edit page when editing a dataset"""
  584. data_list = self.get_history_as_data_list()
  585. self.assertTrue( data_list )
  586. if hid is None: # take last hid
  587. elem = data_list[-1]
  588. hid = int( elem.get('hid') )
  589. self.assertTrue( hid )
  590. self.visit_page( "dataset/edit?hid=%s" % hid )
  591. for subpatt in patt.split():
  592. tc.find(subpatt)
  593. def delete_history_item( self, hda_id, strings_displayed=[] ):
  594. """Deletes an item from a history"""
  595. try:
  596. hda_id = int( hda_id )
  597. except:
  598. raise AssertionError( "Invalid hda_id '%s' - must be int" % hda_id )
  599. self.visit_url( "%s/datasets/%s/delete?show_deleted_on_refresh=False" % ( self.url, self.security.encode_id( hda_id ) ) )
  600. for check_str in strings_displayed:
  601. self.check_page_for_string( check_str )
  602. def undelete_history_item( self, hda_id, strings_displayed=[] ):
  603. """Un-deletes a deleted item in a history"""
  604. try:
  605. hda_id = int( hda_id )
  606. except:
  607. raise AssertionError( "Invalid hda_id '%s' - must be int" % hda_id )
  608. self.visit_url( "%s/datasets/%s/undelete" % ( self.url, self.security.encode_id( hda_id ) ) )
  609. for check_str in strings_displayed:
  610. self.check_page_for_string( check_str )
  611. def display_history_item( self, hda_id, strings_displayed=[] ):
  612. """Displays a history item - simulates eye icon click"""
  613. self.visit_url( '%s/datasets/%s/display/' % ( self.url, self.security.encode_id( hda_id ) ) )
  614. for check_str in strings_displayed:
  615. self.check_page_for_string( check_str )
  616. self.home()
  617. def view_history( self, history_id, strings_displayed=[] ):
  618. """Displays a history for viewing"""
  619. self.visit_url( '%s/history/view?id=%s' % ( self.url, self.security.encode_id( history_id ) ) )
  620. for check_str in strings_displayed:
  621. self.check_page_for_string( check_str )
  622. self.home()
  623. def edit_hda_attribute_info( self, hda_id, new_name='', new_info='', new_dbkey='', new_startcol='',
  624. strings_displayed=[], strings_not_displayed=[] ):
  625. """Edit history_dataset_association attribute information"""
  626. self.home()
  627. self.visit_url( "%s/datasets/%s/edit" % ( self.url, self.security.encode_id( hda_id ) ) )
  628. submit_required = False
  629. self.check_page_for_string( 'Edit Attributes' )
  630. if new_name:
  631. tc.fv( 'edit_attributes', 'name', new_name )
  632. submit_required = True
  633. if new_info:
  634. tc.fv( 'edit_attributes', 'info', new_info )
  635. submit_required = True
  636. if new_dbkey:
  637. tc.fv( 'edit_attributes', 'dbkey', new_dbkey )
  638. submit_required = True
  639. if new_startcol:
  640. tc.fv( 'edit_attributes', 'startCol', new_startcol )
  641. submit_required = True
  642. if submit_required:
  643. tc.submit( 'save' )
  644. self.check_page_for_string( 'Attributes updated' )
  645. for check_str in strings_displayed:
  646. self.check_page_for_string( check_str )
  647. for check_str in strings_not_displayed:
  648. try:
  649. self.check_page_for_string( check_str )
  650. raise AssertionError( "String (%s) incorrectly displayed on Edit Attributes page." % check_str )
  651. except:
  652. pass
  653. self.home()
  654. def check_hda_attribute_info( self, hda_id, strings_displayed=[] ):
  655. """Edit history_dataset_association attribute information"""
  656. for check_str in strings_displayed:
  657. self.check_page_for_string( check_str )
  658. def auto_detect_metadata( self, hda_id ):
  659. """Auto-detect history_dataset_association metadata"""
  660. self.home()
  661. self.visit_url( "%s/datasets/%s/edit" % ( self.url, self.security.encode_id( hda_id ) ) )
  662. self.check_page_for_string( 'This will inspect the dataset and attempt' )
  663. tc.fv( 'auto_detect', 'detect', 'Auto-detect' )
  664. tc.submit( 'detect' )
  665. try:
  666. self.check_page_for_string( 'Attributes have been queued to be updated' )
  667. self.wait()
  668. except AssertionError:
  669. self.check_page_for_string( 'Attributes updated' )
  670. #self.check_page_for_string( 'Attributes updated' )
  671. self.home()
  672. def convert_format( self, hda_id, target_type ):
  673. """Convert format of history_dataset_association"""
  674. self.home()
  675. self.visit_url( "%s/datasets/%s/edit" % ( self.url, self.security.encode_id( hda_id ) ) )
  676. self.check_page_for_string( 'This will inspect the dataset and attempt' )
  677. tc.fv( 'convert_data', 'target_type', target_type )
  678. tc.submit( 'convert_data' )
  679. self.check_page_for_string( 'The file conversion of Convert BED to GFF on data' )
  680. self.wait() # wait for the format convert tool to finish before returning
  681. self.home()
  682. def change_datatype( self, hda_id, datatype ):
  683. """Change format of history_dataset_association"""
  684. self.home()
  685. self.visit_url( "%s/datasets/%s/edit" % ( self.url, self.security.encode_id( hda_id ) ) )
  686. self.check_page_for_string( 'This will change the datatype of the existing dataset but' )
  687. tc.fv( 'change_datatype', 'datatype', datatype )
  688. tc.submit( 'change' )
  689. self.check_page_for_string( 'Changed the type of dataset' )
  690. self.home()
  691. def copy_history_item( self, source_dataset_id=None, target_history_id=None, all_target_history_ids=[],
  692. deleted_history_ids=[] ):
  693. """
  694. Copy 1 history_dataset_association to 1 history (Limited by twill since it doesn't support multiple
  695. field names, such as checkboxes
  696. """
  697. self.home()
  698. self.visit_url( "%s/dataset/copy_datasets?source_dataset_ids=%s" % ( self.url, source_dataset_id ) )
  699. self.check_page_for_string( 'Source History:' )
  700. # Make sure all of users active histories are displayed
  701. for id in all_target_history_ids:
  702. self.check_page_for_string( id )
  703. # Make sure only active histories are displayed
  704. for id in deleted_history_ids:
  705. try:
  706. self.check_page_for_string( id )
  707. raise AssertionError( "deleted history id %d displayed in list of target histories" % id )
  708. except:
  709. pass
  710. tc.fv( '1', 'target_history_id', target_history_id )
  711. tc.submit( 'do_copy' )
  712. check_str = '1 dataset copied to 1 history'
  713. self.check_page_for_string( check_str )
  714. self.home()
  715. def get_hids_in_history( self ):
  716. """Returns the list of hid values for items in a history"""
  717. data_list = self.get_history_as_data_list()
  718. hids = []
  719. for elem in data_list:
  720. hid = elem.get('hid')
  721. hids.append(hid)
  722. return hids
  723. def get_hids_in_histories( self ):
  724. """Returns the list of hids values for items in all histories"""
  725. data_list = self.get_histories_as_data_list()
  726. hids = []
  727. for elem in data_list:
  728. hid = elem.get('hid')
  729. hids.append(hid)
  730. return hids
  731. def makeTfname(self, fname=None):
  732. """safe temp name - preserve the file extension for tools that interpret it"""
  733. suffix = os.path.split(fname)[-1] # ignore full path
  734. fd, temp_prefix = tempfile.mkstemp(prefix='tmp', suffix=suffix)
  735. return temp_prefix
  736. def verify_dataset_correctness( self, filename, hid=None, wait=True, maxseconds=120, attributes=None, shed_tool_id=None ):
  737. """Verifies that the attributes and contents of a history item meet expectations"""
  738. if wait:
  739. self.wait( maxseconds=maxseconds ) # wait for job to finish
  740. data_list = self.get_history_as_data_list()
  741. self.assertTrue( data_list )
  742. if hid is None: # take last hid
  743. elem = data_list[-1]
  744. hid = str( elem.get('hid') )
  745. else:
  746. hid = str( hid )
  747. elems = [ elem for elem in data_list if elem.get('hid') == hid ]
  748. self.assertTrue( len(elems) == 1 )
  749. elem = elems[0]
  750. self.assertTrue( hid )
  751. self._assert_dataset_state( elem, 'ok' )
  752. if filename is not None and self.is_zipped( filename ):
  753. errmsg = 'History item %s is a zip archive which includes invalid files:\n' % hid
  754. zip_file = zipfile.ZipFile( filename, "r" )
  755. name = zip_file.namelist()[0]
  756. test_ext = name.split( "." )[1].strip().lower()
  757. if not ( test_ext == 'scf' or test_ext == 'ab1' or test_ext == 'txt' ):
  758. raise AssertionError( errmsg )
  759. for name in zip_file.namelist():
  760. ext = name.split( "." )[1].strip().lower()
  761. if ext != test_ext:
  762. raise AssertionError( errmsg )
  763. else:
  764. # See not in controllers/root.py about encoded_id.
  765. hda_id = self.security.encode_id( elem.get( 'id' ) )
  766. self.verify_hid( filename, hid=hid, hda_id=hda_id, attributes=attributes, shed_tool_id=shed_tool_id)
  767. def verify_hid( self, filename, hda_id, attributes, shed_tool_id, hid="", dataset_fetcher=None):
  768. dataset_fetcher = dataset_fetcher or self.__default_dataset_fetcher()
  769. data = dataset_fetcher( hda_id )
  770. if attributes is not None and attributes.get( "assert_list", None ) is not None:
  771. try:
  772. verify_assertions(data, attributes["assert_list"])
  773. except AssertionError, err:
  774. errmsg = 'History item %s different than expected\n' % (hid)
  775. errmsg += str( err )
  776. raise AssertionError( errmsg )
  777. if filename is not None:
  778. local_name = self.get_filename( filename, shed_tool_id=shed_tool_id )
  779. temp_name = self.makeTfname(fname=filename)
  780. file( temp_name, 'wb' ).write( data )
  781. # if the server's env has GALAXY_TEST_SAVE, save the output file to that dir
  782. if self.keepOutdir:
  783. ofn = os.path.join( self.keepOutdir, os.path.basename( local_name ) )
  784. log.debug( 'keepoutdir: %s, ofn: %s', self.keepOutdir, ofn )
  785. try:
  786. shutil.copy( temp_name, ofn )
  787. except Exception, exc:
  788. error_log_msg = ( 'TwillTestCase could not save output file %s to %s: ' % ( temp_name, ofn ) )
  789. error_log_msg += str( exc )
  790. log.error( error_log_msg, exc_info=True )
  791. else:
  792. log.debug('## GALAXY_TEST_SAVE=%s. saved %s' % ( self.keepOutdir, ofn ) )
  793. try:
  794. if attributes is None:
  795. attributes = {}
  796. compare = attributes.get( 'compare', 'diff' )
  797. if attributes.get( 'ftype', None ) == 'bam':
  798. local_fh, temp_name = self._bam_to_sam( local_name, temp_name )
  799. local_name = local_fh.name
  800. extra_files = attributes.get( 'extra_files', None )
  801. if compare == 'diff':
  802. self.files_diff( local_name, temp_name, attributes=attributes )
  803. elif compare == 're_match':
  804. self.files_re_match( local_name, temp_name, attributes=attributes )
  805. elif compare == 're_match_multiline':
  806. self.files_re_match_multiline( local_name, temp_name, attributes=attributes )
  807. elif compare == 'sim_size':
  808. delta = attributes.get('delta', '100')
  809. s1 = len(data)
  810. s2 = os.path.getsize(local_name)
  811. if abs(s1 - s2) > int(delta):
  812. raise Exception( 'Files %s=%db but %s=%db - compare (delta=%s) failed' % (temp_name, s1, local_name, s2, delta) )
  813. elif compare == "contains":
  814. self.files_contains( local_name, temp_name, attributes=attributes )
  815. else:
  816. raise Exception( 'Unimplemented Compare type: %s' % compare )
  817. if extra_files:
  818. self.verify_extra_files_content( extra_files, hda_id, shed_tool_id=shed_tool_id, dataset_fetcher=dataset_fetcher )
  819. except AssertionError, err:
  820. errmsg = 'History item %s different than expected, difference (using %s):\n' % ( hid, compare )
  821. errmsg += "( %s v. %s )\n" % ( local_name, temp_name )
  822. errmsg += str( err )
  823. raise AssertionError( errmsg )
  824. finally:
  825. if 'GALAXY_TEST_NO_CLEANUP' not in os.environ:
  826. os.remove( temp_name )
  827. def __default_dataset_fetcher( self ):
  828. def fetcher( hda_id, filename=None ):
  829. if filename is None:
  830. page_url = "display?encoded_id=%s" % hda_id
  831. self.home() # I assume this is not needed.
  832. else:
  833. page_url = "datasets/%s/display/%s" % ( hda_id, filename )
  834. self.visit_page( page_url )
  835. data = self.last_page()
  836. return data
  837. return fetcher
  838. def _bam_to_sam( self, local_name, temp_name ):
  839. temp_local = tempfile.NamedTemporaryFile( suffix='.sam', prefix='local_bam_converted_to_sam_' )
  840. fd, temp_temp = tempfile.mkstemp( suffix='.sam', prefix='history_bam_converted_to_sam_' )
  841. os.close( fd )
  842. p = subprocess.Popen( args='samtools view -h -o "%s" "%s"' % ( temp_local.name, local_name ), shell=True )
  843. assert not p.wait(), 'Converting local (test-data) bam to sam failed'
  844. p = subprocess.Popen( args='samtools view -h -o "%s" "%s"' % ( temp_temp, temp_name ), shell=True )
  845. assert not p.wait(), 'Converting history bam to sam failed'
  846. os.remove( temp_name )
  847. return temp_local, temp_temp
  848. def verify_extra_files_content( self, extra_files, hda_id, dataset_fetcher, shed_tool_id=None ):
  849. files_list = []
  850. for extra_type, extra_value, extra_name, extra_attributes in extra_files:
  851. if extra_type == 'file':
  852. files_list.append( ( extra_name, extra_value, extra_attributes ) )
  853. elif extra_type == 'directory':
  854. for filename in os.listdir( self.get_filename( extra_value, shed_tool_id=shed_tool_id ) ):
  855. files_list.append( ( filename, os.path.join( extra_value, filename ), extra_attributes ) )
  856. else:
  857. raise ValueError( 'unknown extra_files type: %s' % extra_type )
  858. for filename, filepath, attributes in files_list:
  859. self.verify_composite_datatype_file_content( filepath, hda_id, base_name=filename, attributes=attributes, dataset_fetcher=dataset_fetcher, shed_tool_id=shed_tool_id )
  860. def verify_composite_datatype_file_content( self, file_name, hda_id, base_name=None, attributes=None, dataset_fetcher=None, shed_tool_id=None ):
  861. dataset_fetcher = dataset_fetcher or self.__default_dataset_fetcher()
  862. local_name = self.get_filename( file_name, shed_tool_id=shed_tool_id )
  863. if base_name is None:
  864. base_name = os.path.split(file_name)[-1]
  865. temp_name = self.makeTfname(fname=base_name)
  866. data = dataset_fetcher( hda_id, base_name )
  867. file( temp_name, 'wb' ).write( data )
  868. if self.keepOutdir > '':
  869. ofn = os.path.join(self.keepOutdir, base_name)
  870. shutil.copy(temp_name, ofn)
  871. log.debug('## GALAXY_TEST_SAVE=%s. saved %s' % (self.keepOutdir, ofn))
  872. try:
  873. if attributes is None:
  874. attributes = {}
  875. compare = attributes.get( 'compare', 'diff' )
  876. if compare == 'diff':
  877. self.files_diff( local_name, temp_name, attributes=attributes )
  878. elif compare == 're_match':
  879. self.files_re_match( local_name, temp_name, attributes=attributes )
  880. elif compare == 're_match_multiline':
  881. self.files_re_match_multiline( local_name, temp_name, attributes=attributes )
  882. elif compare == 'sim_size':
  883. delta = attributes.get('delta', '100')
  884. s1 = len(data)
  885. s2 = os.path.getsize(local_name)
  886. if abs(s1 - s2) > int(delta):
  887. raise Exception( 'Files %s=%db but %s=%db - compare (delta=%s) failed' % (temp_name, s1, local_name, s2, delta) )
  888. else:
  889. raise Exception( 'Unimplemented Compare type: %s' % compare )
  890. except AssertionError, err:
  891. errmsg = 'Composite file (%s) of History item %s different than expected, difference (using %s):\n' % ( base_name, hda_id, compare )
  892. errmsg += str( err )
  893. raise AssertionError( errmsg )
  894. finally:
  895. if 'GALAXY_TEST_NO_CLEANUP' not in os.environ:
  896. os.remove( temp_name )
  897. def is_zipped( self, filename ):
  898. if not zipfile.is_zipfile( filename ):
  899. return False
  900. return True
  901. def is_binary( self, filename ):
  902. temp = open( filename, "U" ) # why is this not filename? Where did temp_name come from
  903. lineno = 0
  904. for line in temp:
  905. lineno += 1
  906. line = line.strip()
  907. if line:
  908. for char in line:
  909. if ord( char ) > 128:
  910. return True
  911. if lineno > 10:
  912. break
  913. return False
  914. def verify_genome_build( self, dbkey='hg17' ):
  915. """Verifies that the last used genome_build at history id 'hid' is as expected"""
  916. data_list = self.get_history_as_data_list()
  917. self.assertTrue( data_list )
  918. elems = [ elem for elem in data_list ]
  919. elem = elems[-1]
  920. genome_build = elem.get('dbkey')
  921. self.assertTrue( genome_build == dbkey )
  922. # Functions associated with user accounts
  923. def create( self, cntrller='user', email='test@bx.psu.edu', password='testuser', username='admin-user', redirect='' ):
  924. # HACK: don't use panels because late_javascripts() messes up the twill browser and it
  925. # can't find form fields (and hence user can't be logged in).
  926. self.visit_url( "%s/user/create?cntrller=%s&use_panels=False" % ( self.url, cntrller ) )
  927. tc.fv( 'registration', 'email', email )
  928. tc.fv( 'registration', 'redirect', redirect )
  929. tc.fv( 'registration', 'password', password )
  930. tc.fv( 'registration', 'confirm', password )
  931. tc.fv( 'registration', 'username', username )
  932. tc.submit( 'create_user_button' )
  933. previously_created = False
  934. username_taken = False
  935. invalid_username = False
  936. try:
  937. self.check_page_for_string( "Created new user account" )
  938. except:
  939. try:
  940. # May have created the account in a previous test run...
  941. self.check_page_for_string( "User with that email already exists" )
  942. previously_created = True
  943. except:
  944. try:
  945. self.check_page_for_string( 'Public name is taken; please choose another' )
  946. username_taken = True
  947. except:
  948. try:
  949. # Note that we're only checking if the usr name is >< 4 chars here...
  950. self.check_page_for_string( 'Public name must be at least 4 characters in length' )
  951. invalid_username = True
  952. except:
  953. pass
  954. return previously_created, username_taken, invalid_username
  955. def create_user_with_info( self, email, password, username, user_info_values, user_type_fd_id='', cntrller='user',
  956. strings_displayed=[], strings_displayed_after_submit=[] ):
  957. # This method creates a new user with associated info
  958. self.visit_url( "%s/user/create?cntrller=%s&use_panels=False" % ( self.url, cntrller ) )
  959. tc.fv( "1", "email", email )
  960. tc.fv( "1", "password", password )
  961. tc.fv( "1", "confirm", password )
  962. tc.fv( "1", "username", username )
  963. if user_type_fd_id:
  964. # The user_type_fd_id SelectField requires a refresh_on_change
  965. self.refresh_form( 'user_type_fd_id', user_type_fd_id )
  966. tc.fv( "1", "password", password )
  967. tc.fv( "1", "confirm", password )
  968. for index, ( field_name, info_value ) in enumerate( user_info_values ):
  969. tc.fv( "1", field_name, info_value )
  970. for check_str in strings_displayed:
  971. self.check_page_for_string( check_str)
  972. tc.submit( "create_user_button" )
  973. def edit_user_info( self, cntrller='user', id='', new_email='', new_username='', password='', new_password='',
  974. info_values=[], strings_displayed=[], strings_displayed_after_submit=[] ):
  975. if cntrller == 'admin':
  976. url = "%s/admin/users?id=%s&operation=information" % ( self.url, id )
  977. else: # cntrller == 'user:
  978. # The user is editing his own info, so the user id is gotten from trans.user.
  979. url = "%s/user/manage_user_info?cntrller=user" % self.url
  980. self.visit_url( url )
  981. for check_str in strings_displayed:
  982. self.check_page_for_string( check_str )
  983. if new_email or new_username:
  984. if new_email:
  985. tc.fv( "login_info", "email", new_email )
  986. if new_username:
  987. tc.fv( "login_info", "username", new_username )
  988. tc.submit( "login_info_button" )
  989. if password and new_password:
  990. tc.fv( "change_password", "current", password )
  991. tc.fv( "change_password", "password", new_password )
  992. tc.fv( "change_password", "confirm", new_password )
  993. tc.submit( "change_password_button" )
  994. if info_values:
  995. for index, ( field_name, info_value ) in enumerate( info_values ):
  996. tc.fv( "user_info", field_name, info_value )
  997. tc.submit( "edit_user_info_button" )
  998. for check_str in strings_displayed_after_submit:
  999. self.check_page_for_string( check_str )
  1000. self.home()
  1001. def user_set_default_permissions( self, cntrller='user', permissions_out=[], permissions_in=[], role_id='2' ):
  1002. # role.id = 2 is Private Role for test2@bx.psu.edu
  1003. # NOTE: Twill has a bug that requires the ~/user/permissions page to contain at least 1 option value
  1004. # in each select list or twill throws an exception, which is: ParseError: OPTION outside of SELECT
  1005. # Due to this bug, we'll bypass visiting the page, and simply pass the permissions on to the
  1006. # /user/set_default_permissions method.
  1007. url = "user/set_default_permissions?cntrller=%s&update_roles_button=Save&id=None" % cntrller
  1008. for po in permissions_out:
  1009. key = '%s_out' % po
  1010. url = "%s&%s=%s" % ( url, key, str( role_id ) )
  1011. for pi in permissions_in:
  1012. key = '%s_in' % pi
  1013. url = "%s&%s=%s" % ( url, key, str( role_id ) )
  1014. self.visit_url( "%s/%s" % ( self.url, url ) )
  1015. self.check_page_for_string( 'Default new history permissions have been changed.' )
  1016. self.home()
  1017. def history_set_default_permissions( self, permissions_out=[], permissions_in=[], role_id=3 ): # role.id = 3 is Private Role for test3@bx.psu.edu
  1018. # NOTE: Twill has a bug that requires the ~/user/permissions page to contain at least 1 option value
  1019. # in each select list or twill throws an exception, which is: ParseError: OPTION outside of SELECT
  1020. # Due to this bug, we'll bypass visiting the page, and simply pass the permissions on to the
  1021. # /user/set_default_permissions method.
  1022. url = "root/history_set_default_permissions?update_roles_button=Save&id=None&dataset=True"
  1023. for po in permissions_out:
  1024. key = '%s_out' % po
  1025. url = "%s&%s=%s" % ( url, key, str( role_id ) )
  1026. for pi in permissions_in:
  1027. key = '%s_in' % pi
  1028. url = "%s&%s=%s" % ( url, key, str( role_id ) )
  1029. self.home()
  1030. self.visit_url( "%s/%s" % ( self.url, url ) )
  1031. self.check_page_for_string( 'Default history permissions have been changed.' )
  1032. self.home()
  1033. def login( self, email='test@bx.psu.edu', password='testuser', username='admin-user', redirect='' ):
  1034. # test@bx.psu.edu is configured as an admin user
  1035. previously_created, username_taken, invalid_username = \
  1036. self.create( email=email, password=password, username=username, redirect=redirect )
  1037. if previously_created:
  1038. # The acount has previously been created, so just login.
  1039. # HACK: don't use panels because late_javascripts() messes up the twill browser and it
  1040. # can't find form fields (and hence user can't be logged in).
  1041. self.visit_url( "%s/user/login?use_panels=False" % self.url )
  1042. self.submit_form( 1, 'login_button', email=email, redirect=redirect, password=password )
  1043. def logout( self ):
  1044. self.home()
  1045. self.visit_page( "user/logout" )
  1046. self.check_page_for_string( "You have been logged out" )
  1047. self.home()
  1048. # Functions associated with browsers, cookies, HTML forms and page visits
  1049. def check_for_strings( self, strings_displayed=[], strings_not_displayed=[] ):
  1050. if strings_displayed:
  1051. for string in strings_displayed:
  1052. self.check_page_for_string( string )
  1053. if strings_not_displayed:
  1054. for string in strings_not_displayed:
  1055. self.check_string_not_in_page( string )
  1056. def check_page_for_string( self, patt ):
  1057. """Looks for 'patt' in the current browser page"""
  1058. page = self.last_page()
  1059. if page.find( patt ) == -1:
  1060. fname = self.write_temp_file( page )
  1061. errmsg = "no match to '%s'\npage content written to '%s'" % ( patt, fname )
  1062. raise AssertionError( errmsg )
  1063. def check_string_count_in_page( self, patt, min_count ):
  1064. """Checks the number of 'patt' occurrences in the current browser page"""
  1065. page = self.last_page()
  1066. patt_count = page.count( patt )
  1067. # The number of occurrences of patt in the page should be at least min_count
  1068. # so show error if patt_count is less than min_count
  1069. if patt_count < min_count:
  1070. fname = self.write_temp_file( page )
  1071. errmsg = "%i occurrences of '%s' found instead of %i.\npage content written to '%s' " % ( min_count, patt, patt_count, fname )
  1072. raise AssertionError( errmsg )
  1073. def check_string_not_in_page( self, patt ):
  1074. """Checks to make sure 'patt' is NOT in the page."""
  1075. page = self.last_page()
  1076. if page.find( patt ) != -1:
  1077. fname = self.write_temp_file( page )
  1078. errmsg = "string (%s) incorrectly displayed in page.\npage content written to '%s'" % ( patt, fname )
  1079. raise AssertionError( errmsg )
  1080. def check_page(self, strings_displayed, strings_displayed_count, strings_not_displayed):
  1081. """Checks a page for strings displayed, not displayed and number of occurrences of a string"""
  1082. for check_str in strings_displayed:
  1083. self.check_page_for_string( check_str )
  1084. for check_str, count in strings_displayed_count:
  1085. self.check_string_count_in_page( check_str, count )
  1086. for check_str in strings_not_displayed:
  1087. self.check_string_not_in_page( check_str )
  1088. def write_temp_file( self, content, suffix='.html' ):
  1089. fd, fname = tempfile.mkstemp( suffix=suffix, prefix='twilltestcase-' )
  1090. f = os.fdopen( fd, "w" )
  1091. f.write( content )
  1092. f.close()
  1093. return fname
  1094. def clear_cookies( self ):
  1095. tc.clear_cookies()
  1096. def clear_form( self, form=0 ):
  1097. """Clears a form"""
  1098. tc.formclear(str(form))
  1099. def home( self ):
  1100. self.visit_url( self.url )
  1101. def last_page( self ):
  1102. return tc.browser.get_html()
  1103. def load_cookies( self, file, shed_tool_id=None ):
  1104. filename = self.get_filename( file, shed_tool_id=shed_tool_id )
  1105. tc.load_cookies(filename)
  1106. def reload_page( self ):
  1107. tc.reload()
  1108. tc.code(200)
  1109. def show_cookies( self ):
  1110. return tc.show_cookies()
  1111. def showforms( self ):
  1112. """Shows form, helpful for debugging new tests"""
  1113. return tc.showforms()
  1114. def submit_form( self, form_no=0, button="runtool_btn", **kwd ):
  1115. """Populates and submits a form from the keyword arguments."""
  1116. # An HTMLForm contains a sequence of Controls. Supported control classes are:
  1117. # TextControl, FileControl, ListControl, RadioControl, CheckboxControl, SelectControl,
  1118. # SubmitControl, ImageControl
  1119. for i, f in enumerate( self.showforms() ):
  1120. if i == form_no:
  1121. break
  1122. # To help with debugging a tool, print out the form controls when the test fails
  1123. print "form '%s' contains the following controls ( note the values )" % f.name
  1124. controls = {}
  1125. formcontrols = []
  1126. hc_prefix = '<HiddenControl('
  1127. for i, control in enumerate( f.controls ):
  1128. formcontrols.append( "control %d: %s" % ( i, str( control ) ) )
  1129. for i, control in enumerate( f.controls ):
  1130. if not hc_prefix in str( control ):
  1131. try:
  1132. #check if a repeat element needs to be added
  1133. if control.name is not None:
  1134. if control.name not in kwd and control.name.endswith( '_add' ):
  1135. #control name doesn't exist, could be repeat
  1136. repeat_startswith = control.name[0:-4]
  1137. if repeat_startswith and not [ c_name for c_name in controls.keys() if c_name.startswith( repeat_startswith ) ] and [ c_name for c_name in kwd.keys() if c_name.startswith( repeat_startswith ) ]:
  1138. tc.browser.clicked( f, control )
  1139. tc.submit( control.name )
  1140. return self.submit_form( form_no=form_no, button=button, **kwd )
  1141. # Check for refresh_on_change attribute, submit a change if required
  1142. if hasattr( control, 'attrs' ) and 'refresh_on_change' in control.attrs.keys():
  1143. changed = False
  1144. # For DataToolParameter, control.value is the HDA id, but kwd contains the filename.
  1145. # This loop gets the filename/label for the selected values.
  1146. item_labels = [ item.attrs[ 'label' ] for item in control.get_items() if item.selected ]
  1147. for value in kwd[ control.name ]:
  1148. if value not in control.value and True not in [ value in item_label for item_label in item_labels ]:
  1149. changed = True
  1150. break
  1151. if changed:
  1152. # Clear Control and set to proper value
  1153. control.clear()
  1154. # kwd[control.name] should be a singlelist
  1155. for elem in kwd[ control.name ]:
  1156. tc.fv( f.name, control.name, str( elem ) )
  1157. # Create a new submit control, allows form to refresh, instead of going to next page
  1158. control = ClientForm.SubmitControl( 'SubmitControl', '___refresh_grouping___', {'name': 'refresh_grouping'} )
  1159. control.add_to_form( f )
  1160. control.fixup()
  1161. # Submit for refresh
  1162. tc.submit( '___refresh_grouping___' )
  1163. return self.submit_form( form_no=form_no, button=button, **kwd )
  1164. except Exception:
  1165. log.exception( "In submit_form, continuing, but caught exception." )
  1166. for formcontrol in formcontrols:
  1167. log.debug( formcontrol )
  1168. continue
  1169. controls[ control.name ] = control
  1170. # No refresh_on_change attribute found in current form, so process as usual
  1171. for control_name, control_value in kwd.items():
  1172. if control_name not in controls:
  1173. continue # these cannot be handled safely - cause the test to barf out
  1174. if not isinstance( control_value, list ):
  1175. control_value = [ control_value ]
  1176. control = controls[ control_name ]
  1177. control.clear()
  1178. if control.is_of_kind( "text" ):
  1179. tc.fv( f.name, control.name, ",".join( control_value ) )
  1180. elif control.is_of_kind( "list" ):
  1181. try:
  1182. if control.is_of_kind( "multilist" ):
  1183. if control.type == "checkbox":
  1184. def is_checked( value ):
  1185. # Copied from form_builder.CheckboxField
  1186. if value == True:
  1187. return True
  1188. if isinstance( value, list ):
  1189. value = value[0]
  1190. return isinstance( value, basestring ) and value.lower() in ( "yes", "true", "on" )
  1191. try:
  1192. checkbox = control.get()
  1193. checkbox.selected = is_checked( control_value )
  1194. except Exception, e1:
  1195. print "Attempting to set checkbox selected value threw exception: ", e1
  1196. # if there's more than one checkbox, probably should use the behaviour for
  1197. # ClientForm.ListControl ( see twill code ), but this works for now...
  1198. for elem in control_value:
  1199. control.get( name=elem ).selected = True
  1200. else:
  1201. for elem in control_value:
  1202. try:
  1203. # Doubt this case would ever work, but want
  1204. # to preserve backward compat.
  1205. control.get( name=elem ).selected = True
  1206. except Exception:
  1207. # ... anyway this is really what we want to
  1208. # do, probably even want to try the len(
  1209. # elem ) > 30 check below.
  1210. control.get( label=elem ).selected = True
  1211. else: # control.is_of_kind( "singlelist" )
  1212. for elem in control_value:
  1213. try:
  1214. tc.fv( f.name, control.name, str( elem ) )
  1215. except Exception:
  1216. try:
  1217. # Galaxy truncates long file names in the dataset_collector in galaxy/tools/parameters/basic.py
  1218. if len( elem ) > 30:
  1219. elem_name = '%s..%s' % ( elem[:17], elem[-11:] )
  1220. tc.fv( f.name, control.name, str( elem_name ) )
  1221. pass
  1222. else:
  1223. raise
  1224. except Exception:
  1225. raise
  1226. except Exception:
  1227. for formcontrol in formcontrols:
  1228. log.debug( formcontrol )
  1229. log.exception( "Attempting to set control '%s' to value '%s' (also tried '%s') threw exception.", control.name, elem, elem_name )
  1230. pass
  1231. except Exception, exc:
  1232. for formcontrol in formcontrols:
  1233. log.debug( formcontrol )
  1234. errmsg = "Attempting to set field '%s' to value '%s' in form '%s' threw exception: %s\n" % ( control_name, str( control_value ), f.name, str( exc ) )
  1235. errmsg += "control: %s\n" % str( control )
  1236. errmsg += "If the above control is a DataToolparameter whose data type class does not include a sniff() method,\n"
  1237. errmsg += "make sure to include a proper 'ftype' attribute to the tag for the control within the <test> tag set.\n"
  1238. raise AssertionError( errmsg )
  1239. else:
  1240. # Add conditions for other control types here when necessary.
  1241. pass
  1242. tc.submit( button )
  1243. def refresh_form( self, control_name, value, form_no=0, **kwd ):
  1244. """Handles Galaxy's refresh_on_change for forms without ultimately submitting the form"""
  1245. # control_name is the name of the form field that requires refresh_on_change, and value is
  1246. # the value to which that field is being set.
  1247. for i, f in enumerate( self.showforms() ):
  1248. if i == form_no:
  1249. break
  1250. try:
  1251. control = f.find_control( name=control_name )
  1252. except:
  1253. # This assumes we always want the first control of the given name, which may not be ideal...
  1254. control = f.find_control( name=control_name, nr=0 )
  1255. # Check for refresh_on_change attribute, submit a change if required
  1256. if 'refresh_on_change' in control.attrs.keys():
  1257. # Clear Control and set to proper value
  1258. control.clear()
  1259. tc.fv( f.name, control.name, value )
  1260. # Create a new submit control, allows form to refresh, instead of going to next page
  1261. control = ClientForm.SubmitControl( 'SubmitControl', '___refresh_grouping___', {'name': 'refresh_grouping'} )
  1262. control.add_to_form( f )
  1263. control.fixup()
  1264. # Submit for refresh
  1265. tc.submit( '___refresh_grouping___' )
  1266. def visit_page( self, page ):
  1267. # tc.go("./%s" % page)
  1268. if not page.startswith( "/" ):
  1269. page = "/" + page
  1270. tc.go( self.url + page )
  1271. tc.code( 200 )
  1272. def visit_url( self, url ):
  1273. tc.go("%s" % url)
  1274. tc.code( 200 )
  1275. """Functions associated with Galaxy tools"""
  1276. def run_tool( self, tool_id, repeat_name=None, **kwd ):
  1277. tool_id = tool_id.replace(" ", "+")
  1278. """Runs the tool 'tool_id' and passes it the key/values from the *kwd"""
  1279. self.visit_url( "%s/tool_runner/index?tool_id=%s" % (self.url, tool_id) )
  1280. # Must click somewhere in tool_form, to disambiguate what form
  1281. # is being targetted.
  1282. tc.browser.clicked( tc.browser.get_form( 'tool_form' ), None )
  1283. if repeat_name is not None:
  1284. repeat_button = '%s_add' % repeat_name
  1285. # Submit the "repeat" form button to add an input)
  1286. tc.submit( repeat_button )
  1287. tc.find( 'runtool_btn' )
  1288. self.submit_form( **kwd )
  1289. def run_ucsc_main( self, track_params, output_params ):
  1290. """Gets Data From UCSC"""
  1291. tool_id = "ucsc_table_direct1"
  1292. track_string = urllib.urlencode( track_params )
  1293. galaxy_url = urllib.quote_plus( "%s/tool_runner/index?" % self.url )
  1294. self.visit_url( "http://genome.ucsc.edu/cgi-bin/hgTables?GALAXY_URL=%s&hgta_compressType=none&tool_id=%s&%s" % ( galaxy_url, tool_id, track_string ) )
  1295. tc.fv( "mainForm", "hgta_doTopSubmit", "get output" )
  1296. self.submit_form( button="get output" )
  1297. tc.fv( 2, "hgta_doGalaxyQuery", "Send query to Galaxy" )
  1298. self.submit_form( button="Send query to Galaxy" )
  1299. def get_running_datasets( self ):
  1300. self.visit_url( '/api/histories' )
  1301. history_id = from_json_string( self.last_page() )[0][ 'id' ]
  1302. self.visit_url( '/api/histories/%s/contents' % history_id )
  1303. jsondata = from_json_string( self.last_page() )
  1304. for history_item in jsondata:
  1305. self.visit_url( history_item[ 'url' ] )
  1306. item_json = from_json_string( self.last_page() )
  1307. if item_json[ 'state' ] in [ 'queued', 'running', 'paused' ]:
  1308. return True
  1309. return False
  1310. def wait( self, **kwds ):
  1311. """Waits for the tools to finish"""
  1312. return self.wait_for(lambda: self.get_running_datasets(), **kwds)
  1313. def wait_for( self, func, **kwd ):
  1314. sleep_amount = 0.1
  1315. slept = 0
  1316. walltime_exceeded = 86400
  1317. while slept <= walltime_exceeded:
  1318. result = func()
  1319. if result:
  1320. time.sleep( sleep_amount )
  1321. slept += sleep_amount
  1322. sleep_amount *= 2
  1323. if slept + sleep_amount > walltime_exceeded:
  1324. sleep_amount = walltime_exceeded - slept # don't overshoot maxseconds
  1325. else:
  1326. break
  1327. assert slept < walltime_exceeded, 'Tool run exceeded reasonable walltime of 24 hours, terminating.'
  1328. # Dataset Security stuff
  1329. # Tests associated with users
  1330. def create_new_account_as_admin( self, email='test4@bx.psu.edu', password='testuser',
  1331. username='regular-user4', redirect='' ):
  1332. """Create a new account for another user"""
  1333. # HACK: don't use panels because late_javascripts() messes up the twill browser and it
  1334. # can't find form fields (and hence user can't be logged in).
  1335. self.visit_url( "%s/user/create?cntrller=admin" % self.url )
  1336. self.submit_form( 1, 'create_user_button', email=email, redirect=redirect, password=password, confirm=password, username=username )
  1337. previously_created = False
  1338. username_taken = False
  1339. invalid_username = False
  1340. try:
  1341. self.check_page_for_string( "Created new user account" )
  1342. except:
  1343. try:
  1344. # May have created the account in a previous test run...
  1345. self.check_page_for_string( "User with that email already exists" )
  1346. previously_created = True
  1347. except:
  1348. try:
  1349. self.check_page_for_string( 'Public name is taken; please choose another' )
  1350. username_taken = True
  1351. except:
  1352. try:
  1353. # Note that we're only checking if the usr name is >< 4 chars here...
  1354. self.check_page_for_string( 'Public name must be at least 4 characters in length' )
  1355. invalid_username = True
  1356. except:
  1357. pass
  1358. return previously_created, username_taken, invalid_username
  1359. def reset_password_as_admin( self, user_id, password='testreset' ):
  1360. """Reset a user password"""
  1361. self.home()
  1362. self.visit_url( "%s/admin/reset_user_password?id=%s" % ( self.url, user_id ) )
  1363. tc.fv( "1", "password", password )
  1364. tc.fv( "1", "confirm", password )
  1365. tc.submit( "reset_user_password_button" )
  1366. self.check_page_for_string( "Passwords reset for 1 user." )
  1367. self.home()
  1368. def mark_user_deleted( self, user_id, email='' ):
  1369. """Mark a user as deleted"""
  1370. self.home()
  1371. self.visit_url( "%s/admin/users?operation=delete&id=%s" % ( self.url, user_id ) )
  1372. check_str = "Deleted 1 users"
  1373. self.check_page_for_string( check_str )
  1374. self.home()
  1375. def undelete_user( self, user_id, email='' ):
  1376. """Undelete a user"""
  1377. self.home()
  1378. self.visit_url( "%s/admin/users?operation=undelete&id=%s" % ( self.url, user_id ) )
  1379. check_str = "Undeleted 1 users"
  1380. self.check_page_for_string( check_str )
  1381. self.home()
  1382. def purge_user( self, user_id, email ):
  1383. """Purge a user account"""
  1384. self.home()
  1385. self.visit_url( "%s/admin/users?operation=purge&id=%s" % ( self.url, user_id ) )
  1386. check_str = "Purged 1 users"
  1387. self.check_page_for_string( check_str )
  1388. self.home()
  1389. def manage_roles_and_groups_for_user( self, user_id, in_role_ids=[], out_role_ids=[],
  1390. in_group_ids=[], out_group_ids=[], strings_displayed=[] ):
  1391. self.home()
  1392. url = "%s/admin/manage_roles_and_groups_for_user?id=%s" % ( self.url, user_id )
  1393. if in_role_ids:
  1394. url += "&in_roles=%s" % ','.join( in_role_ids )
  1395. if out_role_ids:
  1396. url += "&out_roles=%s" % ','.join( out_role_ids )
  1397. if in_group_ids:
  1398. url += "&in_groups=%s" % ','.join( in_group_ids )
  1399. if out_group_ids:
  1400. url += "&out_groups=%s" % ','.join( out_group_ids )
  1401. if in_role_ids or out_role_ids or in_group_ids or out_group_ids:
  1402. url += "&user_roles_groups_edit_button=Save"
  1403. self.visit_url( url )
  1404. for check_str in strings_displayed:
  1405. self.check_page_for_string( check_str )
  1406. self.home()
  1407. # Tests associated with roles
  1408. def browse_roles( self, strings_displayed=[] ):
  1409. self.visit_url( '%s/admin/roles' % self.url )
  1410. for check_str in strings_displayed:
  1411. self.check_page_for_string( check_str )
  1412. def create_role( self,
  1413. name='Role One',
  1414. description="This is Role One",
  1415. in_user_ids=[],
  1416. in_group_ids=[],
  1417. create_group_for_role='',
  1418. private_role='',
  1419. strings_displayed=[] ):
  1420. """Create a new role"""
  1421. url = "%s/admin/roles?operation=create&create_role_button=Save&name=%s&description=%s" % \
  1422. ( self.url, name.replace( ' ', '+' ), description.replace( ' ', '+' ) )
  1423. if in_user_ids:
  1424. url += "&in_users=%s" % ','.join( in_user_ids )
  1425. if in_group_ids:
  1426. url += "&in_groups=%s" % ','.join( in_group_ids )
  1427. if create_group_for_role == 'yes':
  1428. url += '&create_group_for_role=yes&create_group_for_role=yes'
  1429. self.home()
  1430. self.visit_url( url )
  1431. for check_str in strings_displayed:
  1432. self.check_page_for_string( check_str )
  1433. if private_role:
  1434. # Make sure no private roles are displayed
  1435. try:
  1436. self.check_page_for_string( private_role )
  1437. errmsg = 'Private role %s displayed on Roles page' % private_role
  1438. raise AssertionError( errmsg )
  1439. except AssertionError:
  1440. # Reaching here is the behavior we want since no private roles should be displayed
  1441. pass
  1442. self.home()
  1443. self.visit_url( "%s/admin/roles" % self.url )
  1444. self.check_page_for_string( name )
  1445. self.home()
  1446. def rename_role( self, role_id, name='Role One Renamed', description='This is Role One Re-described' ):
  1447. """Rename a role"""
  1448. self.home()
  1449. self.visit_url( "%s/admin/roles?operation=rename&id=%s" % ( self.url, role_id ) )
  1450. self.check_page_for_string( 'Change role name and description' )
  1451. tc.fv( "1", "name", name )
  1452. tc.fv( "1", "description", description )
  1453. tc.submit( "rename_role_button" )
  1454. self.home()
  1455. def mark_role_deleted( self, role_id, role_name ):
  1456. """Mark a role as deleted"""
  1457. self.home()
  1458. self.visit_url( "%s/admin/roles?operation=delete&id=%s" % ( self.url, role_id ) )
  1459. check_str = "Deleted 1 roles: %s" % role_name
  1460. self.check_page_for_string( check_str )
  1461. self.home()
  1462. def undelete_role( self, role_id, role_name ):
  1463. """Undelete an existing role"""
  1464. self.home()
  1465. self.visit_url( "%s/admin/roles?operation=undelete&id=%s" % ( self.url, role_id ) )
  1466. check_str = "Undeleted 1 roles: %s" % role_name
  1467. self.check_page_for_string( check_str )
  1468. self.home()
  1469. def purge_role( self, role_id, role_name ):
  1470. """Purge an existing role"""
  1471. self.home()
  1472. self.visit_url( "%s/admin/roles?operation=purge&id=%s" % ( self.url, role_id ) )
  1473. check_str = "Purged 1 roles: %s" % role_name
  1474. self.check_page_for_string( check_str )
  1475. self.home()
  1476. def associate_users_and_groups_with_role( self, role_id, role_name, user_ids=[], group_ids=[] ):
  1477. self.home()
  1478. url = "%s/admin/role?id=%s&role_members_edit_button=Save" % ( self.url, role_id )
  1479. if user_ids:
  1480. url += "&in_users=%s" % ','.join( user_ids )
  1481. if group_ids:
  1482. url += "&in_groups=%s" % ','.join( group_ids )
  1483. self.visit_url( url )
  1484. check_str = "Role '%s' has been updated with %d associated users and %d associated groups" % ( role_name, len( user_ids ), len( group_ids ) )
  1485. self.check_page_for_string( check_str )
  1486. self.home()
  1487. # Tests associated with groups
  1488. def create_group( self, name='Group One', in_user_ids=[], in_role_ids=[], create_role_for_group='', strings_displayed=[] ):
  1489. """Create a new group"""
  1490. url = "%s/admin/groups?operation=create&create_group_button=Save&name=%s" % ( self.url, name.replace( ' ', '+' ) )
  1491. if in_user_ids:
  1492. url += "&in_users=%s" % ','.join( in_user_ids )
  1493. if in_role_ids:
  1494. url += "&in_roles=%s" % ','.join( in_role_ids )
  1495. if create_role_for_group == 'yes':
  1496. url += '&create_role_for_group=yes&create_role_for_group=yes'
  1497. self.home()
  1498. self.visit_url( url )
  1499. for check_str in strings_displayed:
  1500. self.check_page_for_string( check_str )
  1501. self.home()
  1502. self.visit_url( "%s/admin/groups" % self.url )
  1503. self.check_page_for_string( name )
  1504. self.home()
  1505. def browse_groups( self, strings_displayed=[] ):
  1506. self.visit_url( '%s/admin/groups' % self.url )
  1507. for check_str in strings_displayed:
  1508. self.check_page_for_string( check_str )
  1509. def rename_group( self, group_id, name='Group One Renamed' ):
  1510. """Rename a group"""
  1511. self.home()
  1512. self.visit_url( "%s/admin/groups?operation=rename&id=%s" % ( self.url, group_id ) )
  1513. self.check_page_for_string( 'Change group name' )
  1514. tc.fv( "1", "name", name )
  1515. tc.submit( "rename_group_button" )
  1516. self.home()
  1517. def associate_users_and_roles_with_group( self, group_id, group_name, user_ids=[], role_ids=[] ):
  1518. self.home()
  1519. url = "%s/admin/manage_users_and_roles_for_group?id=%s&group_roles_users_edit_button=Save" % ( self.url, group_id )
  1520. if user_ids:
  1521. url += "&in_users=%s" % ','.join( user_ids )
  1522. if role_ids:
  1523. url += "&in_roles=%s" % ','.join( role_ids )
  1524. self.visit_url( url )
  1525. check_str = "Group '%s' has been updated with %d associated roles and %d associated users" % ( group_name, len( role_ids ), len( user_ids ) )
  1526. self.check_page_for_string( check_str )
  1527. self.home()
  1528. def mark_group_deleted( self, group_id, group_name ):
  1529. """Mark a group as deleted"""
  1530. self.home()
  1531. self.visit_url( "%s/admin/groups?operation=delete&id=%s" % ( self.url, group_id ) )
  1532. check_str = "Deleted 1 groups: %s" % group_name
  1533. self.check_page_for_string( check_str )
  1534. self.home()
  1535. def undelete_group( self, group_id, group_name ):
  1536. """Undelete an existing group"""
  1537. self.home()
  1538. self.visit_url( "%s/admin/groups?operation=undelete&id=%s" % ( self.url, group_id ) )
  1539. check_str = "Undeleted 1 groups: %s" % group_name
  1540. self.check_page_for_string( check_str )
  1541. self.home()
  1542. def purge_group( self, group_id, group_name ):
  1543. """Purge an existing group"""
  1544. self.home()
  1545. self.visit_url( "%s/admin/groups?operation=purge&id=%s" % ( self.url, group_id ) )
  1546. check_str = "Purged 1 groups: %s" % group_name
  1547. self.check_page_for_string( check_str )
  1548. self.home()
  1549. # Form stuff
  1550. def create_form( self, name, description, form_type, field_type='TextField', form_layout_name='',
  1551. num_fields=1, num_options=0, field_name='1_field_name', strings_displayed=[],
  1552. strings_displayed_after_submit=[] ):
  1553. """Create a new form definition."""
  1554. self.visit_url( "%s/forms/create_form_definition" % self.url )
  1555. for check_str in strings_displayed:
  1556. self.check_page_for_string( check_str )
  1557. tc.fv( "1", "name", name )
  1558. tc.fv( "1", "description", description )
  1559. tc.fv( "1", "form_type_select_field", form_type )
  1560. tc.submit( "create_form_button" )
  1561. if form_type == "Sequencing Sample Form":
  1562. tc.submit( "add_layout_grid" )
  1563. tc.fv( "1", "grid_layout0", form_layout_name )
  1564. # if not adding any fields at this time, remove the default empty field
  1565. if num_fields == 0:
  1566. tc.submit( "remove_button" )
  1567. # Add fields to the new form definition
  1568. for index1 in range( num_fields ):
  1569. field_label = 'field_label_%i' % index1
  1570. field_contents = field_type
  1571. field_help_name = 'field_helptext_%i' % index1
  1572. field_help_contents = 'Field %i help' % index1
  1573. field_default = 'field_default_0'
  1574. field_default_contents = '%s default contents' % form_type
  1575. tc.fv( "1", field_label, field_contents )
  1576. tc.fv( "1", field_help_name, field_help_contents )
  1577. if field_type == 'SelectField':
  1578. # SelectField field_type requires a refresh_on_change
  1579. self.refresh_form( 'field_type_0', field_type )
  1580. # Add options so our select list is functional
  1581. if num_options == 0:
  1582. # Default to 2 options
  1583. num_options = 2
  1584. for index2 in range( 1, num_options + 1 ):
  1585. tc.submit( "addoption_0" )
  1586. # Add contents to the new options fields
  1587. for index2 in range( num_options ):
  1588. option_field_name = 'field_0_option_%i' % index2
  1589. option_field_value = 'Option%i' % index2
  1590. tc.fv( "1", option_field_name, option_field_value )
  1591. else:
  1592. tc.fv( "1", "field_type_0", field_type )
  1593. tc.fv( "1", 'field_name_0', field_name )
  1594. tc.fv( "1", field_default, field_default_contents )
  1595. # All done... now save
  1596. tc.submit( "save_changes_button" )
  1597. for check_str in strings_displayed_after_submit:
  1598. self.check_page_for_string( check_str )
  1599. self.home()
  1600. def edit_form( self, id, form_type='', new_form_name='', new_form_desc='', field_dicts=[], field_index=0,
  1601. strings_displayed=[], strings_not_displayed=[], strings_displayed_after_submit=[] ):
  1602. """Edit form details; name and description"""
  1603. self.home()
  1604. self.visit_url( "%s/forms/edit_form_definition?id=%s" % ( self.url, id ) )
  1605. for check_str in strings_displayed:
  1606. self.check_page_for_string( check_str )
  1607. if new_form_name:
  1608. tc.fv( "1", "name", new_form_name )
  1609. if new_form_desc:
  1610. tc.fv( "1", "description", new_form_desc )
  1611. for i, field_dict in enumerate( field_dicts ):
  1612. index = i + field_index
  1613. tc.submit( "add_field_button" )
  1614. field_label = "field_label_%i" % index
  1615. field_label_value = field_dict[ 'label' ]
  1616. field_help = "field_helptext_%i" % index
  1617. field_help_value = field_dict[ 'desc' ]
  1618. field_type = "field_type_%i" % index
  1619. field_type_value = field_dict[ 'type' ]
  1620. field_required = "field_required_%i" % index
  1621. field_required_value = field_dict[ 'required' ]
  1622. field_name = "field_name_%i" % index
  1623. field_name_value = field_dict.get( 'name', '%i_field_name' % index )
  1624. tc.fv( "1", field_label, field_label_value )
  1625. tc.fv( "1", field_help, field_help_value )
  1626. tc.fv( "1", field_required, field_required_value )
  1627. tc.fv( "1", field_name, field_name_value )
  1628. if field_type_value.lower() == 'selectfield':
  1629. # SelectFields require a refresh_on_change
  1630. self.refresh_form( field_type, field_type_value )
  1631. for option_index, option in enumerate( field_dict[ 'selectlist' ] ):
  1632. tc.submit( "addoption_%i" % index )
  1633. tc.fv( "1", "field_%i_option_%i" % ( index, option_index ), option )
  1634. else:
  1635. tc.fv( "1", field_type, field_type_value )
  1636. tc.submit( "save_changes_button" )
  1637. for check_str in strings_displayed_after_submit:
  1638. self.check_page_for_string( check_str )
  1639. self.home()
  1640. def view_form( self, id, form_type='', form_name='', form_desc='', form_layout_name='', field_dicts=[] ):
  1641. '''View form details'''
  1642. self.home()
  1643. self.visit_url( "%s/forms/view_latest_form_definition?id=%s" % ( self.url, id ) )
  1644. #self.check_page_for_string( form_type )
  1645. self.check_page_for_string( form_name )
  1646. #self.check_page_for_string( form_desc )
  1647. self.check_page_for_string( form_layout_name )
  1648. for i, field_dict in enumerate( field_dicts ):
  1649. self.check_page_for_string( field_dict[ 'label' ] )
  1650. self.check_page_for_string( field_dict[ 'desc' ] )
  1651. self.check_page_for_string( field_dict[ 'type' ] )
  1652. if field_dict[ 'type' ].lower() == 'selectfield':
  1653. for option_index, option in enumerate( field_dict[ 'selectlist' ] ):
  1654. self.check_page_for_string( option )
  1655. self.home()
  1656. def mark_form_deleted( self, form_id ):
  1657. """Mark a form_definition as deleted"""
  1658. self.home()
  1659. url = "%s/forms/delete_form_definition?id=%s" % ( self.url, form_id )
  1660. self.visit_url( url )
  1661. check_str = "1 forms have been deleted."
  1662. self.check_page_for_string( check_str )
  1663. self.home()
  1664. # External services stuff
  1665. def reload_external_service( self, external_service_type_id, strings_displayed=[], strings_displayed_after_submit=[] ):
  1666. self.visit_url( '%s/external_service/reload_external_service_types' % self.url )
  1667. for check_str in strings_displayed:
  1668. self.check_page_for_string( check_str )
  1669. tc.fv( "1", "external_service_type_id", external_service_type_id )
  1670. tc.submit( "reload_external_service_type_button" )
  1671. for check_str in strings_displayed_after_submit:
  1672. self.check_page_for_string( check_str )
  1673. def create_external_service( self, name, description, version, external_service_type_id, field_values={}, strings_displayed=[], strings_displayed_after_submit=[] ):
  1674. self.visit_url( '%s/external_service/create_external_service' % self.url )
  1675. for check_str in strings_displayed:
  1676. self.check_page_for_string( check_str )
  1677. tc.fv( "1", "name", name )
  1678. tc.fv( "1", "description", description )
  1679. tc.fv( "1", "version", version )
  1680. self.refresh_form( "external_service_type_id", external_service_type_id )
  1681. for field, value in field_values.items():
  1682. tc.fv( "1", field, value )
  1683. tc.submit( "create_external_service_button" )
  1684. for check_str in strings_displayed_after_submit:
  1685. self.check_page_for_string( check_str )
  1686. def view_external_service( self, external_service_id, strings_displayed=[] ):
  1687. self.visit_url( '%s/external_service/view_external_service?id=%s' % ( self.url, external_service_id ) )
  1688. for check_str in strings_displayed:
  1689. self.check_page_for_string( check_str )
  1690. def edit_external_service( self, external_service_id, field_values={}, strings_displayed=[], strings_displayed_after_submit=[] ):
  1691. self.visit_url( '%s/external_service/edit_external_service?id=%s' % ( self.url, external_service_id ) )
  1692. for check_str in strings_displayed:
  1693. self.check_page_for_string( check_str )
  1694. for field, value in field_values.items():
  1695. tc.fv( "1", field, value )
  1696. tc.submit( "edit_external_service_button" )
  1697. for check_str in strings_displayed_after_submit:
  1698. self.check_page_for_string( check_str )
  1699. # Sample tracking stuff
  1700. def check_request_grid( self, cntrller, state, deleted=False, strings_displayed=[] ):
  1701. self.visit_url( '%s/%s/browse_requests?sort=create_time&f-state=%s&f-deleted=%s' % \
  1702. ( self.url, cntrller, state.replace( ' ', '+' ), str( deleted ) ) )
  1703. for check_str in strings_displayed:
  1704. self.check_page_for_string( check_str )
  1705. def create_request_type( self, name, desc, request_form_id, sample_form_id, states, strings_displayed=[], strings_displayed_after_submit=[] ):
  1706. self.home()
  1707. self.visit_url( "%s/request_type/create_request_type" % self.url )
  1708. for check_str in strings_displayed:
  1709. self.check_page_for_string( check_str )
  1710. tc.fv( "1", "name", name )
  1711. tc.fv( "1", "desc", desc )
  1712. tc.fv( "1", "request_form_id", request_form_id )
  1713. tc.fv( "1", "sample_form_id", sample_form_id )
  1714. for index, state in enumerate(states):
  1715. tc.fv("1", "state_name_%i" % index, state[0])
  1716. tc.fv("1", "state_desc_%i" % index, state[1])
  1717. tc.submit( "add_state_button" )
  1718. tc.submit( "create_request_type_button" )
  1719. for check_str in strings_displayed_after_submit:
  1720. self.check_page_for_string( check_str )
  1721. def request_type_permissions( self, request_type_id, request_type_name, role_ids_str, permissions_in, permissions_out ):
  1722. # role_ids_str must be a comma-separated string of role ids
  1723. url = "request_type/request_type_permissions?id=%s&update_roles_button=Save" % ( request_type_id )
  1724. for po in permissions_out:
  1725. key = '%s_out' % po
  1726. url = "%s&%s=%s" % ( url, key, role_ids_str )
  1727. for pi in permissions_in:
  1728. key = '%s_in' % pi
  1729. url = "%s&%s=%s" % ( url, key, role_ids_str )
  1730. self.home()
  1731. self.visit_url( "%s/%s" % ( self.url, url ) )
  1732. check_str = "Permissions updated for request type '%s'" % request_type_name
  1733. self.check_page_for_string( check_str )
  1734. self.home()
  1735. def view_request_type( self, request_type_id, request_type_name, sample_states, strings_displayed=[] ):
  1736. '''View request_type details'''
  1737. self.home()
  1738. self.visit_url( "%s/request_type/view_request_type?id=%s" % ( self.url, request_type_id ) )
  1739. self.check_page_for_string( '"%s" request type' % request_type_name )
  1740. for name, desc in sample_states:
  1741. self.check_page_for_string( name )
  1742. self.check_page_for_string( desc )
  1743. for check_str in strings_displayed:
  1744. self.check_page_for_string( check_str )
  1745. def create_request( self, cntrller, request_type_id, name, desc, field_value_tuples, other_users_id='',
  1746. strings_displayed=[], strings_displayed_after_submit=[] ):
  1747. self.visit_url( "%s/requests_common/create_request?cntrller=%s" % ( self.url, cntrller ) )
  1748. # The request_type SelectList requires a refresh_on_change
  1749. self.refresh_form( 'request_type_id', request_type_id )
  1750. if cntrller == 'requests_admin' and other_users_id:
  1751. # The admin is creating a request on behalf of another user
  1752. # The user_id SelectField requires a refresh_on_change so that the selected
  1753. # user's addresses will be populated in the AddressField widget
  1754. self.refresh_form( "user_id", other_users_id )
  1755. for check_str in strings_displayed:
  1756. self.check_page_for_string( check_str )
  1757. tc.fv( "1", "name", name )
  1758. tc.fv( "1", "desc", desc )
  1759. for index, field_value_tuple in enumerate( field_value_tuples ):
  1760. field_index = index + 1
  1761. field_name, field_value, refresh_on_change = field_value_tuple
  1762. if refresh_on_change:
  1763. # Only the AddressField type has a refresh on change setup on selecting an option
  1764. address_option = field_value[0]
  1765. address_value = field_value[1]
  1766. self.refresh_form( field_name, address_option )
  1767. if address_option == 'new':
  1768. # handle new address
  1769. self.check_page_for_string( 'Short address description' )
  1770. for address_field, value in address_value.items():
  1771. tc.fv( "1", field_name+'_'+address_field, value )
  1772. else:
  1773. # existing address
  1774. tc.fv( "1", field_name, address_value )
  1775. else:
  1776. tc.fv( "1", field_name, field_value )
  1777. tc.submit( "create_request_button" )
  1778. for check_str in strings_displayed_after_submit:
  1779. self.check_page_for_string( check_str )
  1780. self.home()
  1781. def view_request( self, cntrller, request_id, strings_displayed=[], strings_displayed_count=[], strings_not_displayed=[] ):
  1782. self.visit_url( "%s/%s/browse_requests?operation=view_request&id=%s" % ( self.url, cntrller, request_id ) )
  1783. self.check_page( strings_displayed, strings_displayed_count, strings_not_displayed )
  1784. def view_request_history( self, cntrller, request_id, strings_displayed=[], strings_displayed_count=[], strings_not_displayed=[] ):
  1785. self.visit_url( "%s/requests_common/view_request_history?cntrller=%s&id=%s" % ( self.url, cntrller, request_id ) )
  1786. self.check_page( strings_displayed, strings_displayed_count, strings_not_displayed )
  1787. def view_sample_history( self, cntrller, sample_id, strings_displayed=[], strings_displayed_count=[], strings_not_displayed=[] ):
  1788. self.visit_url( "%s/requests_common/view_sample_history?cntrller=%s&sample_id=%s" % ( self.url, cntrller, sample_id ) )
  1789. self.check_page( strings_displayed, strings_displayed_count, strings_not_displayed )
  1790. def view_sample_dataset( self, sample_dataset_id, strings_displayed=[], strings_displayed_count=[], strings_not_displayed=[] ):
  1791. self.visit_url( "%s/requests_admin/manage_datasets?operation=view&id=%s" % ( self.url, sample_dataset_id ) )
  1792. self.check_page( strings_displayed, strings_displayed_count, strings_not_displayed )
  1793. def edit_basic_request_info( self, cntrller, request_id, name, new_name='', new_desc='', new_fields=[],
  1794. strings_displayed=[], strings_displayed_after_submit=[] ):
  1795. self.visit_url( "%s/requests_common/edit_basic_request_info?cntrller=%s&id=%s" % ( self.url, cntrller, request_id ) )
  1796. for check_str in strings_displayed:
  1797. self.check_page_for_string( check_str )
  1798. if new_name:
  1799. tc.fv( "1", "name", new_name )
  1800. if new_desc:
  1801. tc.fv( "1", "desc", new_desc )
  1802. for index, ( field_name, field_value ) in enumerate( new_fields ):
  1803. field_name_index = index + 1
  1804. tc.fv( "1", field_name, field_value )
  1805. tc.submit( "edit_basic_request_info_button" )
  1806. for check_str in strings_displayed_after_submit:
  1807. self.check_page_for_string( check_str )
  1808. def edit_request_email_settings( self, cntrller, request_id, check_request_owner=True, additional_emails='',
  1809. check_sample_states=[], strings_displayed=[], strings_displayed_after_submit=[] ):
  1810. self.visit_url( "%s/requests_common/edit_basic_request_info?cntrller=%s&id=%s" % ( self.url, cntrller, request_id ) )
  1811. for check_str in strings_displayed:
  1812. self.check_page_for_string( check_str )
  1813. tc.fv( "2", "email_address", check_request_owner )
  1814. tc.fv( "2", "additional_email_addresses", additional_emails )
  1815. for state_name, state_id, is_checked in check_sample_states:
  1816. tc.fv( "2", "sample_state_%i" % state_id, is_checked )
  1817. tc.submit( "edit_email_settings_button" )
  1818. for check_str in strings_displayed_after_submit:
  1819. self.check_page_for_string( check_str )
  1820. def add_samples( self, cntrller, request_id, sample_value_tuples, folder_options=[], strings_displayed=[], strings_displayed_after_submit=[] ):
  1821. url = "%s/requests_common/add_sample?cntrller=%s&request_id=%s&add_sample_button=Add+sample" % ( self.url, cntrller, request_id )
  1822. self.visit_url( url )
  1823. for check_str in strings_displayed:
  1824. self.check_page_for_string( check_str )
  1825. for sample_index, ( sample_name, target_library_info, sample_field_values ) in enumerate( sample_value_tuples ):
  1826. tc.fv( "1", "sample_%i_name" % sample_index, sample_name )
  1827. tc.fv( "1", "sample_%i_library_id" % sample_index, target_library_info[ 'library' ] )
  1828. self.refresh_form( "sample_%i_library_id" % sample_index, target_library_info[ 'library' ] )
  1829. # check if the folder selectfield has been correctly populated
  1830. for check_str in folder_options:
  1831. self.check_page_for_string( check_str )
  1832. tc.fv( "1", "sample_%i_folder_id" % sample_index, target_library_info[ 'folder' ] )
  1833. for field_index, field_value in enumerate( sample_field_values ):
  1834. tc.fv( "1", "sample_%i_field_%i" % ( sample_index, field_index ), field_value )
  1835. # Do not click on Add sample button when all the sample have been added
  1836. if sample_index < len( sample_value_tuples ) - 1:
  1837. tc.submit( "add_sample_button" )
  1838. # select the correct form before submitting it
  1839. tc.fv( "1", "copy_sample_index", "-1" )
  1840. tc.submit( "save_samples_button" )
  1841. for check_str in strings_displayed_after_submit:
  1842. self.check_page_for_string( check_str )
  1843. def edit_samples( self, cntrller, request_id, sample_value_tuples, strings_displayed=[], strings_displayed_after_submit=[] ):
  1844. url = "%s/requests_common/edit_samples?cntrller=%s&id=%s" % ( self.url, cntrller, request_id )
  1845. self.visit_url( url )
  1846. for check_str in strings_displayed:
  1847. self.check_page_for_string( check_str )
  1848. for sample_index, ( sample_name, target_library_info, sample_field_values ) in enumerate( sample_value_tuples ):
  1849. tc.fv( "1", "sample_%i_name" % sample_index, sample_name )
  1850. tc.fv( "1", "sample_%i_library_id" % sample_index, target_library_info[ 'library' ] )
  1851. self.refresh_form( "sample_%i_library_id" % sample_index, target_library_info[ 'library' ] )
  1852. tc.fv( "1", "sample_%i_folder_id" % sample_index, target_library_info[ 'folder' ] )
  1853. for field_index, field_value in enumerate( sample_field_values ):
  1854. tc.fv( "1", "sample_%i_field_%i" % ( sample_index, field_index ), field_value )
  1855. tc.submit( "save_samples_button" )
  1856. for check_str in strings_displayed_after_submit:
  1857. self.check_page_for_string( check_str )
  1858. def add_bar_codes( self, cntrller, request_id, bar_codes, strings_displayed=[], strings_displayed_after_submit=[] ):
  1859. url = "%s/requests_common/edit_samples?cntrller=%s&id=%s" % ( self.url, cntrller, request_id )
  1860. self.visit_url( url )
  1861. for check_str in strings_displayed:
  1862. self.check_page_for_string( check_str )
  1863. for sample_index, bar_code in enumerate( bar_codes ):
  1864. tc.fv( "1", "sample_%i_bar_code" % sample_index, bar_code )
  1865. tc.submit( "save_samples_button" )
  1866. for check_str in strings_displayed_after_submit:
  1867. self.check_page_for_string( check_str )
  1868. def submit_request( self, cntrller, request_id, request_name, strings_displayed_after_submit=[] ):
  1869. self.visit_url( "%s/requests_common/submit_request?cntrller=%s&id=%s" % ( self.url, cntrller, request_id ) )
  1870. for check_str in strings_displayed_after_submit:
  1871. self.check_page_for_string( check_str )
  1872. def reject_request( self, request_id, request_name, comment, strings_displayed=[], strings_displayed_after_submit=[] ):
  1873. self.visit_url( "%s/requests_admin/reject_request?id=%s" % ( self.url, request_id ) )
  1874. for check_str in strings_displayed:
  1875. self.check_page_for_string( check_str )
  1876. tc.fv( "1", "comment", comment )
  1877. tc.submit( "reject_button" )
  1878. for check_str in strings_displayed_after_submit:
  1879. self.check_page_for_string( check_str )
  1880. def change_sample_state( self, request_id, sample_ids, new_sample_state_id, comment='', strings_displayed=[], strings_displayed_after_submit=[] ):
  1881. url = "%s/requests_common/edit_samples?cntrller=requests_admin&id=%s" % ( self.url, request_id )
  1882. self.visit_url( url )
  1883. for check_str in strings_displayed:
  1884. self.check_page_for_string( check_str )
  1885. for sample_id in sample_ids:
  1886. tc.fv( "1", "select_sample_%i" % sample_id, True )
  1887. tc.fv( "1", "sample_operation", 'Change state' )
  1888. # refresh on change to show the sample states selectfield
  1889. self.refresh_form( "sample_operation", 'Change state' )
  1890. self.check_page_for_string( "Change current state" )
  1891. tc.fv( "1", "sample_state_id", new_sample_state_id )
  1892. tc.fv( "1", "sample_event_comment", comment )
  1893. tc.submit( "save_samples_button" )
  1894. for check_str in strings_displayed_after_submit:
  1895. self.check_page_for_string( check_str )
  1896. def change_sample_target_data_library( self, cntrller, request_id, sample_ids, new_library_id, new_folder_id, folder_options=[], comment='', strings_displayed=[], strings_displayed_after_submit=[] ):
  1897. url = "%s/requests_common/edit_samples?cntrller=%s&id=%s" % ( self.url, cntrller, request_id )
  1898. self.visit_url( url )
  1899. for check_str in strings_displayed:
  1900. self.check_page_for_string( check_str )
  1901. for sample_id in sample_ids:
  1902. tc.fv( "1", "select_sample_%i" % sample_id, True )
  1903. tc.fv( "1", "sample_operation", 'Select data library and folder' )
  1904. # refresh on change to show the data libraries selectfield
  1905. self.refresh_form( "sample_operation", 'Select data library and folder' )
  1906. self.check_page_for_string( "Select data library:" )
  1907. tc.fv( "1", "sample_operation_library_id", new_library_id )
  1908. # refresh on change to show the selectfield with the list of
  1909. # folders in the selected data library above
  1910. self.refresh_form( "sample_operation_library_id", new_library_id )
  1911. self.check_page_for_string( "Select folder:" )
  1912. # check if the folder selectfield has been correctly populated
  1913. for check_str in folder_options:
  1914. self.check_page_for_string( check_str )
  1915. tc.fv( "1", "sample_operation_folder_id", new_folder_id )
  1916. tc.submit( "save_samples_button" )
  1917. for check_str in strings_displayed_after_submit:
  1918. self.check_page_for_string( check_str )
  1919. def add_datasets_to_sample( self, request_id, sample_id, external_service_id, sample_datasets, strings_displayed=[], strings_displayed_after_submit=[] ):
  1920. # visit the dataset selection page
  1921. url = "%s/requests_admin/select_datasets_to_transfer?cntrller=requests_admin&sample_id=%s&request_id=%s&external_service_id=%s" % \
  1922. ( self.url, sample_id, request_id, external_service_id )
  1923. self.visit_url( url )
  1924. for check_str in strings_displayed:
  1925. self.check_page_for_string( check_str )
  1926. # Datasets are associated with the given by the building the appropriate url
  1927. # and calling it as the dataset selection UI is a javascript dynatree
  1928. url = "%s/requests_admin/select_datasets_to_transfer?cntrller=requests_admin&sample_id=%s&request_id=%s" % ( self.url, sample_id, request_id )
  1929. url += '&select_datasets_to_transfer_button=Select%20datasets'
  1930. url += '&selected_datasets_to_transfer=%s' % ','.join( sample_datasets )
  1931. self.visit_url( url )
  1932. for check_str in strings_displayed_after_submit:
  1933. self.check_page_for_string( check_str )
  1934. def rename_sample_datasets( self, sample_id, sample_dataset_ids, new_sample_dataset_names, strings_displayed=[], strings_displayed_after_submit=[] ):
  1935. sample_dataset_ids_string = ','.join( sample_dataset_ids )
  1936. url = "%s/requests_admin/manage_datasets?operation=rename&id=%s" % ( self.url, sample_dataset_ids_string )
  1937. self.visit_url( url )
  1938. for check_str in strings_displayed:
  1939. self.check_page_for_string( check_str )
  1940. for sample_dataset_id, ( prefix, new_name ) in zip( sample_dataset_ids, new_sample_dataset_names ):
  1941. tc.fv( "1", 'rename_datasets_for_sample_%s' % sample_dataset_id, prefix )
  1942. tc.fv( "1", 'new_name_%s' % sample_dataset_id, new_name )
  1943. tc.submit( "rename_datasets_button" )
  1944. for check_str in strings_displayed_after_submit:
  1945. self.check_page_for_string( check_str )
  1946. def delete_sample_datasets( self, sample_id, sample_dataset_ids, strings_displayed=[], strings_displayed_after_submit=[], strings_not_displayed=[] ):
  1947. url = '%s/requests_admin/manage_datasets?cntrller=requests_admin&sample_id=%s' % ( self.url, sample_id )
  1948. self.visit_url( url )
  1949. for check_str in strings_displayed:
  1950. self.check_page_for_string( check_str )
  1951. # simulate selecting datasets and clicking the delete button on the sample datasets grid
  1952. sample_dataset_ids_string = ','.join( sample_dataset_ids )
  1953. url = "%s/requests_admin/manage_datasets?operation=delete&id=%s" % ( self.url, sample_dataset_ids_string )
  1954. self.visit_url( url )
  1955. for check_str in strings_displayed_after_submit:
  1956. self.check_page_for_string( check_str )
  1957. for check_str in strings_not_displayed:
  1958. self.check_string_not_in_page( check_str )
  1959. def start_sample_datasets_transfer( self, sample_id, sample_dataset_ids, strings_displayed=[], strings_displayed_after_submit=[], strings_displayed_count=[], strings_not_displayed=[] ):
  1960. url = '%s/requests_admin/manage_datasets?cntrller=requests_admin&sample_id=%s' % ( self.url, sample_id )
  1961. self.visit_url( url )
  1962. for check_str in strings_displayed:
  1963. self.check_page_for_string( check_str )
  1964. # simulate selecting datasets and clicking the transfer button on the sample datasets grid
  1965. sample_dataset_ids_string = ','.join( sample_dataset_ids )
  1966. url = "%s/requests_admin/manage_datasets?operation=transfer&id=%s" % ( self.url, sample_dataset_ids_string )
  1967. self.visit_url( url )
  1968. for check_str in strings_displayed_after_submit:
  1969. self.check_page_for_string( check_str )
  1970. for check_str in strings_not_displayed:
  1971. self.check_string_not_in_page( check_str )
  1972. for check_str, count in strings_displayed_count:
  1973. self.check_string_count_in_page( check_str, count )
  1974. def add_user_address( self, user_id, address_dict ):
  1975. self.home()
  1976. self.visit_url( "%s/user/new_address?cntrller=user&user_id=%s" % ( self.url, user_id ) )
  1977. self.check_page_for_string( 'Add new address' )
  1978. for field_name, value in address_dict.items():
  1979. tc.fv( "1", field_name, value )
  1980. tc.submit( "new_address_button" )
  1981. self.check_page_for_string( 'Address (%s) has been added' % address_dict[ 'short_desc' ] )
  1982. # Library stuff
  1983. def add_template( self, cntrller, item_type, form_type, form_id, form_name,
  1984. library_id=None, folder_id=None, ldda_id=None, request_type_id=None, sample_id=None ):
  1985. """
  1986. Add a new template to an item - for library items, the template will ALWAYS BE SET TO INHERITABLE here. If you want to
  1987. dis-inherit your template, call the manage_library_template_inheritance() below immediately after you call this
  1988. method in your test code. Templates added to Requesttype objects are always inherited to samples.
  1989. """
  1990. self.home()
  1991. if item_type == 'library':
  1992. url = "%s/library_common/add_template?cntrller=%s&item_type=%s&form_type=%s&library_id=%s" % \
  1993. ( self.url, cntrller, item_type, form_type, library_id )
  1994. elif item_type == 'folder':
  1995. url = "%s/library_common/add_template?cntrller=%s&item_type=%s&form_type=%s&library_id=%s&folder_id=%s" % \
  1996. ( self.url, cntrller, item_type, form_type, library_id, folder_id )
  1997. elif item_type == 'ldda':
  1998. url = "%s/library_common/add_template?cntrller=%s&item_type=%s&form_type=%s&library_id=%s&folder_id=%s&ldda_id=%s" % \
  1999. ( self.url, cntrller, item_type, form_type, library_id, folder_id, ldda_id )
  2000. self.visit_url( url )
  2001. self.check_page_for_string ( "Select a template for the" )
  2002. self.refresh_form( "form_id", form_id )
  2003. # For some unknown reason, twill barfs if the form number ( 1 ) is used in the following
  2004. # rather than the form anme ( select_template ), so we have to use the form name.
  2005. tc.fv( "select_template", "inheritable", '1' )
  2006. tc.submit( "add_template_button" )
  2007. self.check_page_for_string = 'A template based on the form "%s" has been added to this' % form_name
  2008. self.home()
  2009. def manage_library_template_inheritance( self, cntrller, item_type, library_id, folder_id=None, ldda_id=None, inheritable=True ):
  2010. # If inheritable is True, the item is currently inheritable.
  2011. self.home()
  2012. if item_type == 'library':
  2013. url = "%s/library_common/manage_template_inheritance?cntrller=%s&item_type=%s&library_id=%s" % \
  2014. ( self.url, cntrller, item_type, library_id )
  2015. elif item_type == 'folder':
  2016. url = "%s/library_common/manage_template_inheritance?cntrller=%s&item_type=%s&library_id=%s&folder_id=%s" % \
  2017. ( self.url, cntrller, item_type, library_id, folder_id )
  2018. elif item_type == 'ldda':
  2019. url = "%s/library_common/manage_template_inheritance?cntrller=%s&item_type=%s&library_id=%s&folder_id=%s&ldda_id=%s" % \
  2020. ( self.url, cntrller, item_type, library_id, folder_id, ldda_id )
  2021. self.visit_url( url )
  2022. if inheritable:
  2023. self.check_page_for_string = 'will no longer be inherited to contained folders and datasets'
  2024. else:
  2025. self.check_page_for_string = 'will now be inherited to contained folders and datasets'
  2026. self.home()
  2027. def browse_libraries_admin( self, deleted=False, strings_displayed=[], strings_not_displayed=[] ):
  2028. self.visit_url( '%s/library_admin/browse_libraries?sort=name&f-description=All&f-name=All&f-deleted=%s' % ( self.url, str( deleted ) ) )
  2029. for check_str in strings_displayed:
  2030. self.check_page_for_string( check_str )
  2031. for check_str in strings_not_displayed:
  2032. try:
  2033. self.check_page_for_string( check_str )
  2034. raise AssertionError( "String (%s) incorrectly displayed when browing library." % check_str )
  2035. except:
  2036. pass
  2037. def browse_libraries_regular_user( self, strings_displayed=[], strings_not_displayed=[] ):
  2038. self.visit_url( '%s/library/browse_libraries' % self.url )
  2039. for check_str in strings_displayed:
  2040. self.check_page_for_string( check_str )
  2041. for check_str in strings_not_displayed:
  2042. try:
  2043. self.check_page_for_string( check_str )
  2044. raise AssertionError( "String (%s) incorrectly displayed when browing library." % check_str )
  2045. except:
  2046. pass
  2047. def browse_library( self, cntrller, library_id, show_deleted=False, strings_displayed=[], strings_not_displayed=[] ):
  2048. self.visit_url( '%s/library_common/browse_library?cntrller=%s&id=%s&show_deleted=%s' % ( self.url, cntrller, library_id, str( show_deleted ) ) )
  2049. for check_str in strings_displayed:
  2050. self.check_page_for_string( check_str )
  2051. for check_str in strings_not_displayed:
  2052. try:
  2053. self.check_page_for_string( check_str )
  2054. raise AssertionError( "String (%s) incorrectly displayed when browing library." % check_str )
  2055. except:
  2056. pass
  2057. def create_library( self, name='Library One', description='This is Library One', synopsis='Synopsis for Library One' ):
  2058. """Create a new library"""
  2059. self.visit_url( "%s/library_admin/create_library" % self.url )
  2060. self.check_page_for_string( 'Create a new data library' )
  2061. tc.fv( "1", "name", name )
  2062. tc.fv( "1", "description", description )
  2063. tc.fv( "1", "synopsis", synopsis )
  2064. tc.submit( "create_library_button" )
  2065. check_str = "The new library named '%s' has been created" % name
  2066. self.check_page_for_string( check_str )
  2067. self.home()
  2068. def edit_template( self, cntrller, item_type, form_type, library_id, field_type, field_label_1, field_helptext_1, field_default_1,
  2069. folder_id='', ldda_id='', action='add_field' ):
  2070. """Edit the form fields defining a library template"""
  2071. self.visit_url( "%s/library_common/edit_template?cntrller=%s&item_type=%s&form_type=%s&library_id=%s" % \
  2072. ( self.url, cntrller, item_type, form_type, library_id ) )
  2073. self.check_page_for_string( "Edit form definition" )
  2074. if action == 'add_field':
  2075. tc.submit( "add_field_button" )
  2076. tc.fv( "edit_form", "field_label_1", field_label_1 )
  2077. tc.fv( "edit_form", "field_helptext_1", field_helptext_1 )
  2078. if field_type == 'SelectField':
  2079. # Performs a refresh_on_change in this case
  2080. self.refresh_form( "field_type_1", field_type )
  2081. else:
  2082. tc.fv( "edit_form", "field_type_1", field_type )
  2083. tc.fv( "edit_form", "field_default_1", field_default_1 )
  2084. tc.submit( 'save_changes_button' )
  2085. self.check_page_for_string( "The template for this data library has been updated with your changes." )
  2086. def library_info( self, cntrller, library_id, library_name='', new_name='', new_description='', new_synopsis='',
  2087. template_fields=[], strings_displayed=[] ):
  2088. """Edit information about a library, optionally using an existing template with up to 2 elements"""
  2089. self.visit_url( "%s/library_common/library_info?cntrller=%s&id=%s" % ( self.url, cntrller, library_id ) )
  2090. for check_str in strings_displayed:
  2091. self.check_page_for_string( check_str )
  2092. if new_name and new_description and new_synopsis:
  2093. tc.fv( '1', 'name', new_name )
  2094. tc.fv( '1', 'description', new_description )
  2095. tc.fv( '1', 'synopsis', new_synopsis )
  2096. tc.submit( 'library_info_button' )
  2097. self.check_page_for_string( "Information updated for library" )
  2098. if template_fields:
  2099. for field_name, field_value in template_fields:
  2100. # The 2nd form on the page contains the template, and the form is named edit_info.
  2101. # Set the template field value
  2102. tc.fv( "edit_info", field_name, field_value )
  2103. tc.submit( 'edit_info_button' )
  2104. self.home()
  2105. def library_permissions( self, library_id, library_name, role_ids_str, permissions_in, permissions_out, cntrller='library_admin' ):
  2106. # role_ids_str must be a comma-separated string of role ids
  2107. url = "library_common/library_permissions?id=%s&cntrller=%s&update_roles_button=Save" % ( library_id, cntrller )
  2108. for po in permissions_out:
  2109. key = '%s_out' % po
  2110. url = "%s&%s=%s" % ( url, key, role_ids_str )
  2111. for pi in permissions_in:
  2112. key = '%s_in' % pi
  2113. url = "%s&%s=%s" % ( url, key, role_ids_str )
  2114. self.home()
  2115. self.visit_url( "%s/%s" % ( self.url, url ) )
  2116. check_str = "Permissions updated for library '%s'." % library_name
  2117. self.check_page_for_string( check_str )
  2118. self.home()
  2119. def make_library_item_public( self, library_id, id, cntrller='library_admin', item_type='library',
  2120. contents=False, library_name='', folder_name='', ldda_name='' ):
  2121. url = "%s/library_common/make_library_item_public?cntrller=%s&library_id=%s&item_type=%s&id=%s&contents=%s" % \
  2122. ( self.url, cntrller, library_id, item_type, id, str( contents ) )
  2123. self.visit_url( url )
  2124. if item_type == 'library':
  2125. if contents:
  2126. check_str = "The data library (%s) and all it's contents have been made publicly accessible." % library_name
  2127. else:
  2128. check_str = "The data library (%s) has been made publicly accessible, but access to it's contents has been left unchanged." % library_name
  2129. elif item_type == 'folder':
  2130. check_str = "All of the contents of folder (%s) have been made publicly accessible." % folder_name
  2131. elif item_type == 'ldda':
  2132. check_str = "The libary dataset (%s) has been made publicly accessible." % ldda_name
  2133. self.check_page_for_string( check_str )
  2134. # Library folder stuff
  2135. def add_folder( self, cntrller, library_id, folder_id, name='Folder One', description='This is Folder One' ):
  2136. """Create a new folder"""
  2137. url = "%s/library_common/create_folder?cntrller=%s&library_id=%s&parent_id=%s" % ( self.url, cntrller, library_id, folder_id )
  2138. self.visit_url( url )
  2139. self.check_page_for_string( 'Create a new folder' )
  2140. tc.fv( "1", "name", name )
  2141. tc.fv( "1", "description", description )
  2142. tc.submit( "new_folder_button" )
  2143. check_str = "The new folder named '%s' has been added to the data library." % name
  2144. self.check_page_for_string( check_str )
  2145. self.home()
  2146. def folder_info( self, cntrller, folder_id, library_id, name='', new_name='', description='', template_refresh_field_name='1_field_name',
  2147. template_refresh_field_contents='', template_fields=[], strings_displayed=[], strings_not_displayed=[],
  2148. strings_displayed_after_submit=[], strings_not_displayed_after_submit=[] ):
  2149. """Add information to a library using an existing template with 2 elements"""
  2150. self.visit_url( "%s/library_common/folder_info?cntrller=%s&id=%s&library_id=%s" % \
  2151. ( self.url, cntrller, folder_id, library_id ) )
  2152. if name and new_name and description:
  2153. tc.fv( '1', "name", new_name )
  2154. tc.fv( '1', "description", description )
  2155. tc.submit( 'rename_folder_button' )
  2156. for check_str in strings_displayed:
  2157. self.check_page_for_string( check_str )
  2158. for check_str in strings_not_displayed:
  2159. try:
  2160. self.check_page_for_string( check_str )
  2161. raise AssertionError( "String (%s) incorrectly displayed." % check_str )
  2162. except:
  2163. pass
  2164. if template_refresh_field_contents:
  2165. # A template containing an AddressField is displayed on the form, so we need to refresh the form
  2166. # with the received template_refresh_field_contents. There are 2 forms on the folder_info page
  2167. # when in edit mode, and the 2nd one is the one we want.
  2168. self.refresh_form( template_refresh_field_name, template_refresh_field_contents, form_no=2 )
  2169. if template_fields:
  2170. # We have an information template associated with the folder, so
  2171. # there are 2 forms on this page and the template is the 2nd form
  2172. for field_name, field_value in template_fields:
  2173. tc.fv( "edit_info", field_name, field_value )
  2174. tc.submit( 'edit_info_button' )
  2175. for check_str in strings_displayed_after_submit:
  2176. self.check_page_for_string( check_str )
  2177. for check_str in strings_not_displayed_after_submit:
  2178. try:
  2179. self.check_page_for_string( check_str )
  2180. raise AssertionError( "String (%s) incorrectly displayed." % check_str )
  2181. except:
  2182. pass
  2183. self.home()
  2184. # Library dataset stuff
  2185. def upload_library_dataset( self, cntrller, library_id, folder_id, filename='', server_dir='', replace_id='',
  2186. upload_option='upload_file', file_type='auto', dbkey='hg18', space_to_tab='',
  2187. link_data_only='copy_files', preserve_dirs='Yes', filesystem_paths='', roles=[],
  2188. ldda_message='', hda_ids='', template_refresh_field_name='1_field_name',
  2189. template_refresh_field_contents='', template_fields=[], show_deleted='False', strings_displayed=[] ):
  2190. """Add datasets to library using any upload_option"""
  2191. # NOTE: due to the library_wait() method call at the end of this method, no tests should be done
  2192. # for strings_displayed_after_submit.
  2193. url = "%s/library_common/upload_library_dataset?cntrller=%s&library_id=%s&folder_id=%s" % \
  2194. ( self.url, cntrller, library_id, folder_id )
  2195. if replace_id:
  2196. # If we're uploading a new version of a library dataset, we have to include the replace_id param in the
  2197. # request because the form field named replace_id will not be displayed on the upload form if we dont.
  2198. url += "&replace_id=%s" % replace_id
  2199. self.visit_url( url )
  2200. if template_refresh_field_contents:
  2201. # A template containing an AddressField is displayed on the upload form, so we need to refresh the form
  2202. # with the received template_refresh_field_contents.
  2203. self.refresh_form( template_refresh_field_name, template_refresh_field_contents )
  2204. for tup in template_fields:
  2205. tc.fv( "1", tup[0], tup[1] )
  2206. tc.fv( "1", "library_id", library_id )
  2207. tc.fv( "1", "folder_id", folder_id )
  2208. tc.fv( "1", "show_deleted", show_deleted )
  2209. tc.fv( "1", "ldda_message", ldda_message )
  2210. tc.fv( "1", "file_type", file_type )
  2211. tc.fv( "1", "dbkey", dbkey )
  2212. if space_to_tab:
  2213. tc.fv( "1", "space_to_tab", space_to_tab )
  2214. for role_id in roles:
  2215. tc.fv( "1", "roles", role_id )
  2216. # Refresh the form by selecting the upload_option - we do this here to ensure
  2217. # all previously entered form contents are retained.
  2218. self.refresh_form( 'upload_option', upload_option )
  2219. if upload_option == 'import_from_history':
  2220. for check_str in strings_displayed:
  2221. self.check_page_for_string( check_str )
  2222. if hda_ids:
  2223. # Twill cannot handle multi-checkboxes, so the form can only have 1 hda_ids checkbox
  2224. try:
  2225. tc.fv( "add_history_datasets_to_library", "hda_ids", hda_ids )
  2226. except:
  2227. tc.fv( "add_history_datasets_to_library", "hda_ids", '1' )
  2228. tc.submit( 'add_history_datasets_to_library_button' )
  2229. else:
  2230. if upload_option in [ 'upload_paths', 'upload_directory' ]:
  2231. tc.fv( "1", "link_data_only", link_data_only )
  2232. if upload_option == 'upload_paths':
  2233. tc.fv( "1", "filesystem_paths", filesystem_paths )
  2234. if upload_option == 'upload_directory' and server_dir:
  2235. tc.fv( "1", "server_dir", server_dir )
  2236. if upload_option == 'upload_file':
  2237. if filename:
  2238. filename = self.get_filename( filename )
  2239. tc.formfile( "1", "files_0|file_data", filename )
  2240. for check_str in strings_displayed:
  2241. self.check_page_for_string( check_str )
  2242. tc.submit( "runtool_btn" )
  2243. # Give the files some time to finish uploading
  2244. self.library_wait( library_id )
  2245. self.home()
  2246. def ldda_permissions( self, cntrller, library_id, folder_id, id, role_ids_str,
  2247. permissions_in=[], permissions_out=[], strings_displayed=[], ldda_name='' ):
  2248. # role_ids_str must be a comma-separated string of role ids
  2249. url = "%s/library_common/ldda_permissions?cntrller=%s&library_id=%s&folder_id=%s&id=%s" % \
  2250. ( self.url, cntrller, library_id, folder_id, id )
  2251. for po in permissions_out:
  2252. key = '%s_out' % po
  2253. url = "%s&%s=%s" % ( url, key, role_ids_str )
  2254. for pi in permissions_in:
  2255. key = '%s_in' % pi
  2256. url = "%s&%s=%s" % ( url, key, role_ids_str )
  2257. if permissions_in or permissions_out:
  2258. url += "&update_roles_button=Save"
  2259. self.visit_url( url )
  2260. if not strings_displayed:
  2261. strings_displayed = [ "Permissions updated for dataset '%s'." % ldda_name ]
  2262. for check_str in strings_displayed:
  2263. self.check_page_for_string( check_str )
  2264. self.home()
  2265. def ldda_info( self, cntrller, library_id, folder_id, ldda_id, strings_displayed=[], strings_not_displayed=[] ):
  2266. """View library_dataset_dataset_association information"""
  2267. self.visit_url( "%s/library_common/ldda_info?cntrller=%s&library_id=%s&folder_id=%s&id=%s" % \
  2268. ( self.url, cntrller, library_id, folder_id, ldda_id ) )
  2269. for check_str in strings_displayed:
  2270. self.check_page_for_string( check_str )
  2271. for check_str in strings_not_displayed:
  2272. try:
  2273. self.check_page_for_string( check_str )
  2274. raise AssertionError( "String (%s) should not have been displayed on ldda info page." % check_str )
  2275. except:
  2276. pass
  2277. self.home()
  2278. def ldda_edit_info( self, cntrller, library_id, folder_id, ldda_id, ldda_name, new_ldda_name='', template_refresh_field_name='1_field_name',
  2279. template_refresh_field_contents='', template_fields=[], strings_displayed=[], strings_not_displayed=[] ):
  2280. """Edit library_dataset_dataset_association information, optionally template element information"""
  2281. self.visit_url( "%s/library_common/ldda_edit_info?cntrller=%s&library_id=%s&folder_id=%s&id=%s" % \
  2282. ( self.url, cntrller, library_id, folder_id, ldda_id ) )
  2283. check_str = 'Edit attributes of %s' % ldda_name
  2284. self.check_page_for_string( check_str )
  2285. if new_ldda_name:
  2286. tc.fv( '1', 'name', new_ldda_name )
  2287. tc.submit( 'save' )
  2288. check_str = "Attributes updated for library dataset '%s'." % new_ldda_name
  2289. self.check_page_for_string( check_str )
  2290. if template_refresh_field_contents:
  2291. # A template containing an AddressField is displayed on the upload form, so we need to refresh the form
  2292. # with the received template_refresh_field_contents. There are 4 forms on this page, and the template is
  2293. # contained in the 4th form named "edit_info".
  2294. self.refresh_form( template_refresh_field_name, template_refresh_field_contents, form_no=4 )
  2295. if template_fields:
  2296. # We have an information template associated with the folder, so
  2297. # there are 2 forms on this page and the template is the 2nd form
  2298. for field_name, field_value in template_fields:
  2299. tc.fv( "edit_info", field_name, field_value )
  2300. tc.submit( 'edit_info_button' )
  2301. for check_str in strings_displayed:
  2302. self.check_page_for_string( check_str )
  2303. for check_str in strings_not_displayed:
  2304. try:
  2305. self.check_page_for_string( check_str )
  2306. raise AssertionError( "String (%s) should not have been displayed on ldda Edit Attributes page." % check_str )
  2307. except:
  2308. pass
  2309. self.home()
  2310. def act_on_multiple_datasets( self, cntrller, library_id, do_action, ldda_ids='', strings_displayed=[] ):
  2311. # Can't use the ~/library_admin/libraries form as twill barfs on it so we'll simulate the form submission
  2312. # by going directly to the form action
  2313. self.visit_url( '%s/library_common/act_on_multiple_datasets?cntrller=%s&library_id=%s&ldda_ids=%s&do_action=%s' \
  2314. % ( self.url, cntrller, library_id, ldda_ids, do_action ) )
  2315. for check_str in strings_displayed:
  2316. self.check_page_for_string( check_str )
  2317. def import_datasets_to_histories( self, cntrller, library_id, ldda_ids='', new_history_name='Unnamed history', strings_displayed=[] ):
  2318. # Can't use the ~/library_admin/libraries form as twill barfs on it so we'll simulate the form submission
  2319. # by going directly to the form action
  2320. self.visit_url( '%s/library_common/import_datasets_to_histories?cntrller=%s&library_id=%s&ldda_ids=%s&new_history_name=%s&import_datasets_to_histories_button=Import+library+datasets' \
  2321. % ( self.url, cntrller, library_id, ldda_ids, new_history_name ) )
  2322. for check_str in strings_displayed:
  2323. self.check_page_for_string( check_str )
  2324. def download_archive_of_library_files( self, cntrller, library_id, ldda_ids, format ):
  2325. self.home()
  2326. # Here it would be ideal to have twill set form values and submit the form, but
  2327. # twill barfs on that due to the recently introduced page wrappers around the contents
  2328. # of the browse_library.mako template which enable panel layout when visiting the
  2329. # page from an external URL. By "barfs", I mean that twill somehow loses hod on the
  2330. # cntrller param. We'll just simulate the form submission by building the URL manually.
  2331. # Here's the old, better approach...
  2332. #self.visit_url( "%s/library_common/browse_library?cntrller=%s&id=%s" % ( self.url, cntrller, library_id ) )
  2333. #for ldda_id in ldda_ids:
  2334. # tc.fv( "1", "ldda_ids", ldda_id )
  2335. #tc.fv( "1", "do_action", format )
  2336. #tc.submit( "action_on_datasets_button" )
  2337. # Here's the new approach...
  2338. url = "%s/library_common/act_on_multiple_datasets?cntrller=%s&library_id=%s&do_action=%s" \
  2339. % ( self.url, cntrller, library_id, format )
  2340. for ldda_id in ldda_ids:
  2341. url += "&ldda_ids=%s" % ldda_id
  2342. self.visit_url( url )
  2343. tc.code( 200 )
  2344. archive = self.write_temp_file( self.last_page(), suffix='.' + format )
  2345. self.home()
  2346. return archive
  2347. def check_archive_contents( self, archive, lddas ):
  2348. def get_ldda_path( ldda ):
  2349. path = ""
  2350. parent_folder = ldda.library_dataset.folder
  2351. while parent_folder is not None:
  2352. if parent_folder.parent is None:
  2353. path = os.path.join( parent_folder.library_root[0].name, path )
  2354. break
  2355. path = os.path.join( parent_folder.name, path )
  2356. parent_folder = parent_folder.parent
  2357. path += ldda.name
  2358. return path
  2359. def mkdir( file ):
  2360. dir = os.path.join( tmpd, os.path.dirname( file ) )
  2361. if not os.path.exists( dir ):
  2362. os.makedirs( dir )
  2363. tmpd = tempfile.mkdtemp()
  2364. if tarfile.is_tarfile( archive ):
  2365. t = tarfile.open( archive )
  2366. for n in t.getnames():
  2367. mkdir( n )
  2368. t.extract( n, tmpd )
  2369. t.close()
  2370. elif zipfile.is_zipfile( archive ):
  2371. z = zipfile.ZipFile( archive, 'r' )
  2372. for n in z.namelist():
  2373. mkdir( n )
  2374. open( os.path.join( tmpd, n ), 'wb' ).write( z.read( n ) )
  2375. z.close()
  2376. else:
  2377. raise Exception( 'Unable to read archive: %s' % archive )
  2378. for ldda in lddas:
  2379. orig_file = self.get_filename( ldda.name )
  2380. downloaded_file = os.path.join( tmpd, get_ldda_path( ldda ) )
  2381. assert os.path.exists( downloaded_file )
  2382. try:
  2383. self.files_diff( orig_file, downloaded_file )
  2384. except AssertionError, err:
  2385. errmsg = 'Library item %s different than expected, difference:\n' % ldda.name
  2386. errmsg += str( err )
  2387. errmsg += 'Unpacked archive remains in: %s\n' % tmpd
  2388. raise AssertionError( errmsg )
  2389. shutil.rmtree( tmpd )
  2390. def move_library_item( self, cntrller, item_type, item_id, source_library_id, make_target_current,
  2391. target_library_id='', target_folder_id='', strings_displayed=[], strings_displayed_after_submit=[] ):
  2392. self.home()
  2393. self.visit_url( "%s/library_common/move_library_item?cntrller=%s&item_type=%s&item_id=%s&source_library_id=%s&make_target_current=%s" \
  2394. % ( self.url, cntrller, item_type, item_id, source_library_id, make_target_current ) )
  2395. if target_library_id:
  2396. self.refresh_form( 'target_library_id', target_library_id )
  2397. if target_folder_id:
  2398. tc.fv( '1', 'target_folder_id', target_folder_id )
  2399. for check_str in strings_displayed:
  2400. self.check_page_for_string( check_str )
  2401. tc.submit( 'move_library_item_button' )
  2402. for check_str in strings_displayed_after_submit:
  2403. self.check_page_for_string( check_str )
  2404. self.home()
  2405. def delete_library_item( self, cntrller, library_id, item_id, item_name, item_type='library_dataset' ):
  2406. """Mark a library item as deleted"""
  2407. self.home()
  2408. self.visit_url( "%s/library_common/delete_library_item?cntrller=%s&library_id=%s&item_id=%s&item_type=%s" \
  2409. % ( self.url, cntrller, library_id, item_id, item_type ) )
  2410. if item_type == 'library_dataset':
  2411. item_desc = 'Dataset'
  2412. else:
  2413. item_desc = item_type.capitalize()
  2414. check_str = "marked deleted"
  2415. self.check_page_for_string( check_str )
  2416. self.home()
  2417. def undelete_library_item( self, cntrller, library_id, item_id, item_name, item_type='library_dataset' ):
  2418. """Mark a library item as deleted"""
  2419. self.home()
  2420. self.visit_url( "%s/library_common/undelete_library_item?cntrller=%s&library_id=%s&item_id=%s&item_type=%s" \
  2421. % ( self.url, cntrller, library_id, item_id, item_type ) )
  2422. if item_type == 'library_dataset':
  2423. item_desc = 'Dataset'
  2424. else:
  2425. item_desc = item_type.capitalize()
  2426. check_str = "marked undeleted"
  2427. self.check_page_for_string( check_str )
  2428. self.home()
  2429. def purge_library( self, library_id, library_name ):
  2430. """Purge a library"""
  2431. self.home()
  2432. self.visit_url( "%s/library_admin/purge_library?id=%s" % ( self.url, library_id ) )
  2433. check_str = "Library '%s' and all of its contents have been purged" % library_name
  2434. self.check_page_for_string( check_str )
  2435. self.home()
  2436. def library_wait( self, library_id, cntrller='library_admin', maxiter=90 ):
  2437. """Waits for the tools to finish"""
  2438. count = 0
  2439. sleep_amount = 1
  2440. while count < maxiter:
  2441. count += 1
  2442. self.visit_url( "%s/library_common/browse_library?cntrller=%s&id=%s" % ( self.url, cntrller, library_id ) )
  2443. page = tc.browser.get_html()
  2444. if page.find( '<!-- running: do not change this comment, used by TwillTestCase.library_wait -->' ) > -1:
  2445. time.sleep( sleep_amount )
  2446. sleep_amount += 1
  2447. else:
  2448. break
  2449. self.assertNotEqual(count, maxiter)
  2450. # Tests associated with tags
  2451. def add_tag( self, item_id, item_class, context, new_tag ):
  2452. self.visit_url( "%s/tag/add_tag_async?item_id=%s&item_class=%s&context=%s&new_tag=%s" % \
  2453. ( self.url, item_id, item_class, context, new_tag ) )
  2454. def get_tags( self, item_id, item_class ):
  2455. self.visit_url( "%s/tag/get_tagging_elt_async?item_id=%s&item_class=%s" % \
  2456. ( self.url, item_id, item_class ) )