galaxy-commits
Threads by month
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
February 2013
- 2 participants
- 189 discussions
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/c8b7dfb1e76d/
changeset: c8b7dfb1e76d
branch: bugfixes
user: Bjoern Gruening
date: 2013-02-09 16:48:20
summary: Display the toolshed tools in the workflow search.
affected #: 1 file
diff -r 506484344db3a370f8ae24096041d38557d1967e -r c8b7dfb1e76d45532a339592f72c57918fbe6ba6 templates/webapps/galaxy/workflow/editor.mako
--- a/templates/webapps/galaxy/workflow/editor.mako
+++ b/templates/webapps/galaxy/workflow/editor.mako
@@ -93,12 +93,12 @@
$(".toolSectionWrapper").find(".toolTitle").hide();
if ( data.length != 0 ) {
// Map tool ids to element ids and join them.
- var s = $.map( data, function( n, i ) { return "#link-" + n; } ).join( ", " );
+ var s = $.map( data, function( n, i ) { return "link-" + n; } );
// First pass to show matching tools and their parents.
- $(s).each( function() {
+ $(s).each( function(index,id) {
// Add class to denote match.
- $(this).parent().addClass("search_match");
- $(this).parent().show().parent().parent().show().parent().show();
+ $("[id='"+id+"']").parent().addClass("search_match");
+ $("[id='"+id+"']").parent().show().parent().parent().show().parent().show();
});
// Hide labels that have no visible children.
$(".toolPanelLabel").each( function() {
https://bitbucket.org/galaxy/galaxy-central/commits/a6710d64e2d8/
changeset: a6710d64e2d8
user: dannon
date: 2013-02-13 19:15:00
summary: Merged in BjoernGruening/galaxy-central-bgruening/bugfixes (pull request #120)
Display the toolshed tools in the workflow search.
affected #: 1 file
diff -r 0ea9f5196fde94701cf2ec8404e9e33e53c51d0e -r a6710d64e2d8081e07050696e377c5f864dd1f9f templates/webapps/galaxy/workflow/editor.mako
--- a/templates/webapps/galaxy/workflow/editor.mako
+++ b/templates/webapps/galaxy/workflow/editor.mako
@@ -93,12 +93,12 @@
$(".toolSectionWrapper").find(".toolTitle").hide();
if ( data.length != 0 ) {
// Map tool ids to element ids and join them.
- var s = $.map( data, function( n, i ) { return "#link-" + n; } ).join( ", " );
+ var s = $.map( data, function( n, i ) { return "link-" + n; } );
// First pass to show matching tools and their parents.
- $(s).each( function() {
+ $(s).each( function(index,id) {
// Add class to denote match.
- $(this).parent().addClass("search_match");
- $(this).parent().show().parent().parent().show().parent().show();
+ $("[id='"+id+"']").parent().addClass("search_match");
+ $("[id='"+id+"']").parent().show().parent().parent().show().parent().show();
});
// Hide labels that have no visible children.
$(".toolPanelLabel").each( function() {
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: carlfeberhard: Patch base.less for select2-container, min-width; Fix .hgignore to handle new static/style path
by Bitbucket 13 Feb '13
by Bitbucket 13 Feb '13
13 Feb '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/0ea9f5196fde/
changeset: 0ea9f5196fde
user: carlfeberhard
date: 2013-02-13 19:12:33
summary: Patch base.less for select2-container, min-width; Fix .hgignore to handle new static/style path
affected #: 3 files
diff -r 06b9f3ddce047c8db70e892732441a35cc91b241 -r 0ea9f5196fde94701cf2ec8404e9e33e53c51d0e .hgignore
--- a/.hgignore
+++ b/.hgignore
@@ -79,7 +79,7 @@
# CSS build artifacts.
*/variables.less
-static/june_2007_style/blue/base_sprites.less
+static/style/blue/base_sprites.less
# Testing
selenium-server.jar
diff -r 06b9f3ddce047c8db70e892732441a35cc91b241 -r 0ea9f5196fde94701cf2ec8404e9e33e53c51d0e static/style/base.less
--- a/static/style/base.less
+++ b/static/style/base.less
@@ -5,6 +5,10 @@
@import "fontawesome/font-awesome.less";
@import "select2.less";
+/* fix for zero width select2 - remove when fixed there */
+.select2-container {
+ min-width: 256px;
+}
// Mixins
diff -r 06b9f3ddce047c8db70e892732441a35cc91b241 -r 0ea9f5196fde94701cf2ec8404e9e33e53c51d0e static/style/blue/base.css
--- a/static/style/blue/base.css
+++ b/static/style/blue/base.css
@@ -886,7 +886,8 @@
.select2-result-selectable .select2-match,.select2-result-unselectable .select2-result-selectable .select2-match{text-decoration:underline;}
.select2-result-unselectable .select2-match{text-decoration:none;}
.select2-offscreen{position:absolute;left:-10000px;}
-@media only screen and (-webkit-min-device-pixel-ratio:1.5){.select2-search input,.select2-search-choice-close,.select2-container .select2-choice abbr,.select2-container .select2-choice div b{background-image:url(select2x2.png) !important;background-repeat:no-repeat !important;background-size:60px 40px !important;} .select2-search input{background-position:100% -21px !important;}}.unselectable{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;}
+@media only screen and (-webkit-min-device-pixel-ratio:1.5){.select2-search input,.select2-search-choice-close,.select2-container .select2-choice abbr,.select2-container .select2-choice div b{background-image:url(select2x2.png) !important;background-repeat:no-repeat !important;background-size:60px 40px !important;} .select2-search input{background-position:100% -21px !important;}}.select2-container{min-width:256px;}
+.unselectable{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;}
.parent-width{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;width:100%;*width:90%;}
.clear{*zoom:1;}.clear:before,.clear:after{display:table;content:"";line-height:0;}
.clear:after{clear:both;}
@@ -1078,7 +1079,7 @@
.action-button [class^="fa-icon-"].fa-icon-spin.icon-large,.action-button [class*=" fa-icon-"].fa-icon-spin.icon-large{height:.75em;}
a.action-button{text-decoration:none;}
.action-button>img{vertical-align:middle;}
-.action-button:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);color:inherit;}
+.action-button:active{color:inherit;}
.menubutton{*display:inline;*zoom:1;padding:4px 12px;margin-bottom:0;font-size:12px;line-height:16px;text-align:center;vertical-align:middle;color:#333333;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);background-color:#f5f5f5;background-image:-moz-linear-gradient(top, #ffffff, #e6e6e6);background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(top, #ffffff, #e6e6e6);background-image:-o-linear-gradient(top, #ffffff, #e6e6e6);background-image:linear-gradient(to bottom, #ffffff, #e6e6e6);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);*background-color:#e6e6e6;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border:1px solid #999999;*border:0;border-bottom-color:#808080;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*margin-left:.3em;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);border-color:#c5c5c5;border-color:rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25);padding:2px 10px 2px;border-color:#999999;border-color:rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.4);display:inline-block;cursor:pointer;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none;}.menubutton:hover,.menubutton:active,.menubutton.active,.menubutton.disabled,.menubutton[disabled]{color:#333333;background-color:#e6e6e6;*background-color:#d9d9d9;}
.menubutton:active,.menubutton.active{background-color:#cccccc \9;}
.menubutton:hover,.menubutton:active,.menubutton.active,.menubutton.disabled,.menubutton[disabled]{color:#333333;background-color:#e6e6e6;*background-color:#d9d9d9;}
@@ -1092,7 +1093,7 @@
.menubutton [class^="fa-icon-"],.menubutton [class*=" fa-icon-"]{display:inline;line-height:.6em;}.menubutton [class^="fa-icon-"].fa-icon-spin,.menubutton [class*=" fa-icon-"].fa-icon-spin{display:inline-block;}
.menubutton [class^="fa-icon-"].pull-left.fa-icon-2x,.menubutton [class*=" fa-icon-"].pull-left.fa-icon-2x,.menubutton [class^="fa-icon-"].pull-right.fa-icon-2x,.menubutton [class*=" fa-icon-"].pull-right.fa-icon-2x{margin-top:.35em;}
.menubutton [class^="fa-icon-"].fa-icon-spin.icon-large,.menubutton [class*=" fa-icon-"].fa-icon-spin.icon-large{height:.75em;}
-.menubutton:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05);color:inherit;}
+.menubutton:active{color:inherit;}
.menubutton:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
.menubutton a{text-decoration:none;}
.menubutton .label,.menubutton>label{position:relative;display:inline-block;border-right:none;text-decoration:none;text-align:left;max-height:32px;line-height:16px;overflow:hidden;text-overflow:ellipsis;}
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/06b9f3ddce04/
changeset: 06b9f3ddce04
user: greg
date: 2013-02-13 18:40:11
summary: Fix tool shed functional test that uploaded a tar archive without properly setting the option to not remove files from the repository that are not included in the uploaded archive. This resulted in setting metadata on the repository after upload in such a way that functional tests were not properly testing behavior.
affected #: 1 file
diff -r 6b10699dc0950691097400e83fa6b51e35501e6f -r 06b9f3ddce047c8db70e892732441a35cc91b241 test/tool_shed/functional/test_0400_repository_component_reviews.py
--- a/test/tool_shed/functional/test_0400_repository_component_reviews.py
+++ b/test/tool_shed/functional/test_0400_repository_component_reviews.py
@@ -452,7 +452,10 @@
"""
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
user = test_db_util.get_user( common.test_user_2_email )
- self.upload_file( repository, 'filtering/filtering_test_data.tar', commit_message="Uploaded test data." )
+ self.upload_file( repository,
+ 'filtering/filtering_test_data.tar',
+ commit_message="Uploaded test data.",
+ remove_repo_files_not_in_tar='No' )
def test_0110_review_new_changeset_functional_tests( self ):
'''Update the filtering repository's readme component review to reflect the presence of the readme file.'''
"""
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
3 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/91c6fa5712b5/
changeset: 91c6fa5712b5
user: jmchilton
date: 2013-02-10 18:13:51
summary: Add optional "upload" attribute to tool definitions.
When extracting workflows, such tools are treated as inputs. This eliminates the need for the hack of hardcoding 'upload1' in tools.py and allows multiple upload tools to exist and function properly when extracting workflows.
affected #: 2 files
diff -r 506484344db3a370f8ae24096041d38557d1967e -r 91c6fa5712b5eaac805b576418de699e216fa384 lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -1085,7 +1085,7 @@
if requirements_elem:
self.parse_requirements( requirements_elem )
# Determine if this tool can be used in workflows
- self.is_workflow_compatible = self.check_workflow_compatible()
+ self.is_workflow_compatible = self.check_workflow_compatible(root)
# Trackster configuration.
trackster_conf = root.find( "trackster_conf" )
if trackster_conf is not None:
@@ -1653,7 +1653,7 @@
version = requirement_elem.get( "version", None )
requirement = ToolRequirement( name=name, type=type, version=version )
self.requirements.append( requirement )
- def check_workflow_compatible( self ):
+ def check_workflow_compatible( self, root ):
"""
Determine if a tool can be used in workflows. External tools and the
upload tool are currently not supported by workflows.
@@ -1666,9 +1666,7 @@
# right now
if self.tool_type.startswith( 'data_source' ):
return False
- # HACK: upload is (as always) a special case becuase file parameters
- # can't be persisted.
- if self.id == "upload1":
+ if util.string_as_bool( root.get( "upload", "False" ) ):
return False
# TODO: Anyway to capture tools that dynamically change their own
# outputs?
diff -r 506484344db3a370f8ae24096041d38557d1967e -r 91c6fa5712b5eaac805b576418de699e216fa384 tools/data_source/upload.xml
--- a/tools/data_source/upload.xml
+++ b/tools/data_source/upload.xml
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
-<tool name="Upload File" id="upload1" version="1.1.3">
+<tool name="Upload File" id="upload1" version="1.1.3" upload="true"><description>
from your computer
</description>
https://bitbucket.org/galaxy/galaxy-central/commits/46b01a48a40d/
changeset: 46b01a48a40d
user: jmchilton
date: 2013-02-13 17:45:56
summary: Based on input from natefoo, replace root tool tag "upload" with inverse tag "workflow_compatible". Adjust logic in tools module accordingly.
affected #: 2 files
diff -r 91c6fa5712b5eaac805b576418de699e216fa384 -r 46b01a48a40d70ec5d5fa9c3a21a3e2dec4cee24 lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -1666,7 +1666,7 @@
# right now
if self.tool_type.startswith( 'data_source' ):
return False
- if util.string_as_bool( root.get( "upload", "False" ) ):
+ if not util.string_as_bool( root.get( "workflow_compatible", "True" ) ):
return False
# TODO: Anyway to capture tools that dynamically change their own
# outputs?
diff -r 91c6fa5712b5eaac805b576418de699e216fa384 -r 46b01a48a40d70ec5d5fa9c3a21a3e2dec4cee24 tools/data_source/upload.xml
--- a/tools/data_source/upload.xml
+++ b/tools/data_source/upload.xml
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
-<tool name="Upload File" id="upload1" version="1.1.3" upload="true">
+<tool name="Upload File" id="upload1" version="1.1.3" workflow_compatible="false"><description>
from your computer
</description>
https://bitbucket.org/galaxy/galaxy-central/commits/6b10699dc095/
changeset: 6b10699dc095
user: natefoo
date: 2013-02-13 17:51:56
summary: Merged in jmchilton/galaxy-central-allow-additional-upload-tools (pull request #122)
Add optional "upload" attribute to tool definitions.
affected #: 2 files
diff -r c19fa5da36cc51fd5ee6005ed801a7e2c9aba229 -r 6b10699dc0950691097400e83fa6b51e35501e6f lib/galaxy/tools/__init__.py
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -1097,7 +1097,7 @@
if requirements_elem:
self.parse_requirements( requirements_elem )
# Determine if this tool can be used in workflows
- self.is_workflow_compatible = self.check_workflow_compatible()
+ self.is_workflow_compatible = self.check_workflow_compatible(root)
# Trackster configuration.
trackster_conf = root.find( "trackster_conf" )
if trackster_conf is not None:
@@ -1665,7 +1665,7 @@
version = requirement_elem.get( "version", None )
requirement = ToolRequirement( name=name, type=type, version=version )
self.requirements.append( requirement )
- def check_workflow_compatible( self ):
+ def check_workflow_compatible( self, root ):
"""
Determine if a tool can be used in workflows. External tools and the
upload tool are currently not supported by workflows.
@@ -1678,9 +1678,7 @@
# right now
if self.tool_type.startswith( 'data_source' ):
return False
- # HACK: upload is (as always) a special case becuase file parameters
- # can't be persisted.
- if self.id == "upload1":
+ if not util.string_as_bool( root.get( "workflow_compatible", "True" ) ):
return False
# TODO: Anyway to capture tools that dynamically change their own
# outputs?
diff -r c19fa5da36cc51fd5ee6005ed801a7e2c9aba229 -r 6b10699dc0950691097400e83fa6b51e35501e6f tools/data_source/upload.xml
--- a/tools/data_source/upload.xml
+++ b/tools/data_source/upload.xml
@@ -1,6 +1,6 @@
<?xml version="1.0"?>
-<tool name="Upload File" id="upload1" version="1.1.3">
+<tool name="Upload File" id="upload1" version="1.1.3" workflow_compatible="false"><description>
from your computer
</description>
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
13 Feb '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/c19fa5da36cc/
changeset: c19fa5da36cc
user: jgoecks
date: 2013-02-13 17:04:54
summary: Improved documentation in bowtie2 loc file.
affected #: 1 file
diff -r 7bf8f6a1da9c7d6148712e574f16a34a2c6e1525 -r c19fa5da36cc51fd5ee6005ed801a7e2c9aba229 tool-data/bowtie2_indices.loc.sample
--- a/tool-data/bowtie2_indices.loc.sample
+++ b/tool-data/bowtie2_indices.loc.sample
@@ -1,37 +1,37 @@
-#This is a sample file distributed with Galaxy that enables tools
-#to use a directory of Bowtie2 indexed sequences data files. You will
-#need to create these data files and then create a bowtie_indices.loc
-#file similar to this one (store it in this directory) that points to
-#the directories in which those files are stored. The bowtie2_indices.loc
-#file has this format (longer white space characters are TAB characters):
+# bowtie2_indices.loc.sample
+# This is a *.loc.sample file distributed with Galaxy that enables tools
+# to use a directory of indexed data files. This one is for Bowtie2 and Tophat2.
+# See the wiki: http://wiki.galaxyproject.org/Admin/NGS%20Local%20Setup
+# First create these data files and save them in your own data directory structure.
+# Then, create a bowtie_indices.loc file to use those indexes with tools.
+# Copy this file, save it with the same name (minus the .sample),
+# follow the format examples, and store the result in this directory.
+# The file should include an one line entry for each index set.
+# The path points to the "basename" for the set, not a specific file.
+# It has four text columns seperated by TABS.
#
-#<unique_build_id><dbkey><display_name><file_base_path>
+# <unique_build_id><dbkey><display_name><file_base_path>
#
-#So, for example, if you had hg18 indexed stored in
-#/depot/data2/galaxy/bowtie2/hg18/,
-#then the bowtie2_indices.loc entry would look like this:
+# So, for example, if you had hg18 indexes stored in:
#
-#hg18 hg18 hg18 /depot/data2/galaxy/bowtie2/hg18/hg18
+# /depot/data2/galaxy/hg19/bowtie2/
#
-#and your /depot/data2/galaxy/bowtie2/hg18/ directory
-#would contain hg18.*.ebwt files:
+# containing hg19 genome and hg19.*.bt2 files, such as:
+# -rw-rw-r-- 1 james james 914M Feb 10 18:56 hg19canon.fa
+# -rw-rw-r-- 1 james james 914M Feb 10 18:56 hg19canon.1.bt2
+# -rw-rw-r-- 1 james james 683M Feb 10 18:56 hg19canon.2.bt2
+# -rw-rw-r-- 1 james james 3.3K Feb 10 16:54 hg19canon.3.bt2
+# -rw-rw-r-- 1 james james 683M Feb 10 16:54 hg19canon.4.bt2
+# -rw-rw-r-- 1 james james 914M Feb 10 20:45 hg19canon.rev.1.bt2
+# -rw-rw-r-- 1 james james 683M Feb 10 20:45 hg19canon.rev.2.bt2
#
-#-rw-r--r-- 1 james universe 830134 2005-09-13 10:12 hg18.1.ebwt
-#-rw-r--r-- 1 james universe 527388 2005-09-13 10:12 hg18.2.ebwt
-#-rw-r--r-- 1 james universe 269808 2005-09-13 10:12 hg18.3.ebwt
-#...etc...
+# then the bowtie2_indices.loc entry could look like this:
#
-#Your bowtie2_indices.loc file should include an entry per line for each
-#index set you have stored. The "file" in the path does not actually
-#exist, but it is the prefix for the actual index files. For example:
+#hg19 hg19 Human (hg19) /depot/data2/galaxy/hg19/bowtie2/hg19canon
#
-#hg18canon hg18 hg18 Canonical /depot/data2/galaxy/bowtie2/hg18/hg18canon
-#hg18full hg18 hg18 Full /depot/data2/galaxy/bowtie2/hg18/hg18full
-#/orig/path/hg19 hg19 hg19 /depot/data2/galaxy/bowtie2/hg19/hg19
-#...etc...
+#More examples:
#
-#Note that for backwards compatibility with workflows, the unique ID of
-#an entry must be the path that was in the original loc file, because that
-#is the value stored in the workflow for that parameter. That is why the
-#hg19 entry above looks odd. New genomes can be better-looking.
+#mm10 mm10 Mouse (mm10) /depot/data2/galaxy/mm10/bowtie2/mm10
+#dm3 dm3 D. melanogaster (dm3) /depot/data2/galaxy/mm10/bowtie2/dm3
#
+#
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: inithello: Tool shed functional test enhancements and documentation.
by Bitbucket 13 Feb '13
by Bitbucket 13 Feb '13
13 Feb '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/7bf8f6a1da9c/
changeset: 7bf8f6a1da9c
user: inithello
date: 2013-02-13 16:58:10
summary: Tool shed functional test enhancements and documentation.
affected #: 3 files
diff -r 3eff396a81d833a2dcabce2fd34b53badf707984 -r 7bf8f6a1da9c7d6148712e574f16a34a2c6e1525 test/tool_shed/base/test_db_util.py
--- a/test/tool_shed/base/test_db_util.py
+++ b/test/tool_shed/base/test_db_util.py
@@ -1,9 +1,11 @@
-import galaxy.model
+import galaxy.model, logging
import galaxy.webapps.community.model as model
from galaxy.model.orm import *
from galaxy.webapps.community.model.mapping import context as sa_session
from galaxy.model.mapping import context as ga_session
+log = logging.getLogger( 'test.tool_shed.test_db_util' )
+
def delete_obj( obj ):
sa_session.delete( obj )
sa_session.flush()
@@ -57,10 +59,44 @@
if role.name == user.email and role.description == 'Private Role for %s' % user.email:
return role
raise AssertionError( "Private role not found for user '%s'" % user.email )
+def get_repository_reviews( repository_id, reviewer_user_id=None, changeset_revision=None ):
+ if reviewer_user_id and changeset_revision:
+ reviews = sa_session.query( model.RepositoryReview ) \
+ .filter( and_( model.RepositoryReview.table.c.repository_id == repository_id,
+ model.RepositoryReview.table.c.deleted == False,
+ model.RepositoryReview.table.c.changeset_revision == changeset_revision,
+ model.RepositoryReview.table.c.user_id == reviewer_user_id ) ) \
+ .all()
+ elif reviewer_user_id:
+ reviews = sa_session.query( model.RepositoryReview ) \
+ .filter( and_( model.RepositoryReview.table.c.repository_id == repository_id,
+ model.RepositoryReview.table.c.deleted == False,
+ model.RepositoryReview.table.c.user_id == reviewer_user_id ) ) \
+ .all()
+ else:
+ reviews = sa_session.query( model.RepositoryReview ) \
+ .filter( and_( model.RepositoryReview.table.c.repository_id == repository_id,
+ model.RepositoryReview.table.c.deleted == False ) ) \
+ .all()
+ return reviews
+def get_reviews_ordered_by_changeset_revision( repository_id, changelog_tuples, reviewer_user_id=None ):
+ reviews = get_repository_reviews( repository_id, reviewer_user_id=reviewer_user_id )
+ ordered_reviews = []
+ for ctx_rev, changeset_hash in changelog_tuples:
+ for review in reviews:
+ if str( review.changeset_revision ) == str( changeset_hash ):
+ ordered_reviews.append( review )
+ return ordered_reviews
def get_repository_by_id( repository_id ):
return sa_session.query( model.Repository ) \
.filter( model.Repository.table.c.id == repository_id ) \
.first()
+def get_repository_downloadable_revisions( repository_id ):
+ revisions = sa_session.query( model.RepositoryMetadata ) \
+ .filter( and_( model.RepositoryMetadata.table.c.repository_id == repository_id,
+ model.RepositoryMetadata.table.c.downloadable == True ) ) \
+ .all()
+ return revisions
def get_repository_review_by_user_id_changeset_revision( user_id, repository_id, changeset_revision ):
review = sa_session.query( model.RepositoryReview ) \
.filter( and_( model.RepositoryReview.table.c.user_id == user_id,
diff -r 3eff396a81d833a2dcabce2fd34b53badf707984 -r 7bf8f6a1da9c7d6148712e574f16a34a2c6e1525 test/tool_shed/base/twilltestcase.py
--- a/test/tool_shed/base/twilltestcase.py
+++ b/test/tool_shed/base/twilltestcase.py
@@ -493,6 +493,14 @@
return os.path.abspath( os.path.join( filepath, filename ) )
else:
return os.path.abspath( os.path.join( self.file_dir, filename ) )
+ def get_last_reviewed_revision_by_user( self, user, repository ):
+ changelog_tuples = self.get_repository_changelog_tuples( repository )
+ reviews = test_db_util.get_reviews_ordered_by_changeset_revision( repository.id, changelog_tuples, reviewer_user_id = user.id )
+ if reviews:
+ last_review = reviews[ -1 ]
+ else:
+ last_review = None
+ return last_review
def get_or_create_repository( self, owner=None, strings_displayed=[], strings_not_displayed=[], **kwd ):
repository = test_db_util.get_repository_by_name_and_owner( kwd[ 'name' ], owner )
if repository is None:
@@ -508,9 +516,13 @@
return self.hgweb_config_manager.get_entry( lhs )
except:
raise Exception( "Entry for repository %s missing in hgweb config file %s." % ( lhs, self.hgweb_config_manager.hgweb_config ) )
- def get_repository_changelog( self, repository ):
+ def get_repository_changelog_tuples( self, repository ):
repo = hg.repository( ui.ui(), self.get_repo_path( repository ) )
- return [ ( repo.changectx( changeset ), changeset ) for changeset in repo.changelog ]
+ changelog_tuples = []
+ for changeset in repo.changelog:
+ ctx = repo.changectx( changeset )
+ changelog_tuples.append( ( ctx.rev(), repo.changectx( changeset ) ) )
+ return changelog_tuples
def get_repository_datatypes_count( self, repository ):
metadata = self.get_repository_metadata( repository )[0].metadata
if 'datatypes' not in metadata:
diff -r 3eff396a81d833a2dcabce2fd34b53badf707984 -r 7bf8f6a1da9c7d6148712e574f16a34a2c6e1525 test/tool_shed/functional/test_0400_repository_component_reviews.py
--- a/test/tool_shed/functional/test_0400_repository_component_reviews.py
+++ b/test/tool_shed/functional/test_0400_repository_component_reviews.py
@@ -5,10 +5,45 @@
repository_description = 'Galaxy filtering tool for test 0400'
repository_long_description = 'Long description of Galaxy filtering tool for test 0400'
+'''
+1. Create users.
+2. Grant reviewer role to test_user_2.
+3. Check that the review components that are to be tested are defined in this tool shed instance.
+4. Create a repository, owned by test_user_1, to be reviewed by test_user_2.
+5. Review the datatypes component on the repository.
+6. Check that no other components besides datatypes display as reviewed.
+7. Review the functional tests component on the repository.
+8. Check that only functional tests and datatypes display as reviewed.
+9. Review the readme component on the repository.
+10. Check that only functional tests, datatypes, and readme display as reviewed.
+11. Review the repository dependencies component.
+12. Check that only repository dependencies, functional tests, datatypes, and readme display as reviewed.
+13. Review the tool dependencies component.
+14. Check that only tool dependencies, repository dependencies, functional tests, datatypes, and readme display as reviewed.
+15. Review the tools component.
+16. Check that only tools, tool dependencies, repository dependencies, functional tests, datatypes, and readme display as reviewed.
+17. Review the workflows component.
+18. Check that all components display as reviewed.
+19. Upload readme.txt to the repository.
+20. Copy the previous review, and update the readme component review to reflect the existence of a readme file.
+21. Check that the readme component review has been updated, and the other component reviews are present.
+22. Upload test data to the repository. This will also create a new changeset revision.
+23. Review the functional tests component on the repository, copying the other components from the previous review.
+24. Verify that the functional tests component review has been updated, and as in step 21, the other reviews are unchanged.
+25. Upload a new version of the tool.
+26. Review the new revision's functional tests component.
+27. Verify that the functional tests component review displays correctly.
+'''
+
class TestRepositoryComponentReviews( ShedTwillTestCase ):
'''Test repository component review features.'''
def test_0000_initiate_users( self ):
"""Create necessary user accounts and login as an admin user."""
+ """
+ We are at step 1.
+ Create all the user accounts that are needed for this test script to run independently of other test.
+ Previously created accounts will not be re-created.
+ """
self.logout()
self.login( email=common.test_user_1_email, username=common.test_user_1_name )
test_user_1 = test_db_util.get_user( common.test_user_1_email )
@@ -26,11 +61,21 @@
admin_user_private_role = test_db_util.get_private_role( admin_user )
def test_0005_grant_reviewer_role( self ):
'''Grant the repository reviewer role to test_user_2.'''
+ """
+ We are at step 2.
+ We now have an admin user (admin_user) and two non-admin users (test_user_1 and test_user_2). Grant the repository
+ reviewer role to test_user_2, who will not be the owner of the reviewed repositories.
+ """
reviewer_role = test_db_util.get_role_by_name( 'Repository Reviewer' )
test_user_2 = test_db_util.get_user( common.test_user_2_email )
self.grant_role_to_user( test_user_2, reviewer_role )
def test_0010_verify_repository_review_components( self ):
'''Ensure that the required review components exist.'''
+ """
+ We are at step 3.
+ We now have an admin user (admin_user) and two non-admin users (test_user_1 and test_user_2). Grant the repository
+ reviewer role to test_user_2, who will not be the owner of the reviewed repositories.
+ """
strings_not_displayed=[ 'Repository dependencies' ]
self.manage_review_components( strings_not_displayed=strings_not_displayed )
self.add_repository_review_component( name='Repository dependencies',
@@ -39,6 +84,11 @@
self.manage_review_components( strings_displayed=strings_displayed )
def test_0015_create_repository( self ):
"""Create and populate the filtering repository"""
+ """
+ We are at step 4.
+ Log in as test_user_1 and create the filtering repository, then upload a basic set of
+ components to be reviewed in subsequent tests.
+ """
category = self.create_category( name='Test 0400 Repository Component Reviews', description='Test 0400 Repository Component Reviews' )
self.logout()
self.login( email=common.test_user_1_email, username=common.test_user_1_name )
@@ -53,21 +103,32 @@
self.upload_file( repository, 'filtering/filtering_1.1.0.tar', commit_message="Uploaded filtering 1.1.0" )
def test_0020_review_initial_revision_data_types( self ):
'''Review the datatypes component for the current tip revision.'''
+ """
+ We are at step 5.
+ Log in as test_user_2 and review the data types component of the filtering repository owned by test_user_1.
# Review this revision:
# Data types (N/A)
- # Functional tests (One star, comment 'functional tests missing')
- # README (N/A)
- # Repository dependencies (N/A)
- # Tool dependencies (N/A)
- # Tools (5 stars, good review)
- # Workflows (N/A)
+ """
self.logout()
self.login( email=common.test_user_2_email, username=common.test_user_2_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
+ # The create_repository_review method takes a dict( component label=review contents ).
+ # If review_contents is empty, it marks that component as not applicable. The review
+ # contents dict should have the structure:
+ # {
+ # rating: 1-5,
+ # comment: <text>
+ # approved: yes/no
+ # private: yes/no
+ # }
review_contents_dict = { 'Data types': dict() }
self.create_repository_review( repository, review_contents_dict )
def test_0025_verify_datatype_review( self ):
'''Verify that the datatypes component review displays correctly.'''
+ """
+ We are at step 6.
+ Log in as test_user_1 and check that the filtering repository only has a review for the data types component.
+ """
self.logout()
self.login( email=common.test_user_1_email, username=common.test_user_1_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
@@ -76,11 +137,28 @@
strings_not_displayed = [ 'Functional tests', 'README', 'Repository dependencies', 'Tool dependencies', 'Tools', 'Workflows' ]
self.verify_repository_reviews( repository, reviewer=user, strings_displayed=strings_displayed )
def test_0030_review_initial_revision_functional_tests( self ):
- '''Review the datatypes component for the current tip revision.'''
+ '''Review the functional tests component for the current tip revision.'''
+ """
+ We are at step 7.
+ Log in as test_user_2 and review the functional tests component for this repository. Since the repository
+ has not been altered, this will update the existing review to add a component.
+ # Review this revision:
+ # Data types (N/A)
+ # Functional tests (One star, comment 'functional tests missing')
+ """
self.logout()
self.login( email=common.test_user_2_email, username=common.test_user_2_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
user = test_db_util.get_user( common.test_user_2_email )
+ # The create_repository_review method takes a dict( component label=review contents ).
+ # If review_contents is empty, it marks that component as not applicable. The review
+ # contents dict should have the structure:
+ # {
+ # rating: 1-5,
+ # comment: <text>
+ # approved: yes/no
+ # private: yes/no
+ # }
review_contents_dict = { 'Functional tests': dict( rating=1, comment='Functional tests missing', approved='no', private='yes' ) }
self.review_repository( repository, review_contents_dict, user )
# def test_0030_verify_review_display( self ):
@@ -89,7 +167,13 @@
# self.logout()
# self.login( email=common.test_user_3_email, username=common.test_user_3_name )
def test_0035_verify_functional_test_review( self ):
- '''Verify that the datatypes component review displays correctly.'''
+ '''Verify that the functional tests component review displays correctly.'''
+ """
+ We are at step 8.
+ Log in as test_user_1 and check that the filtering repository now has reviews
+ for the data types and functional tests components. Since the functional tests component was not marked as 'Not applicable',
+ also check for the review comment.
+ """
self.logout()
self.login( email=common.test_user_1_email, username=common.test_user_1_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
@@ -99,14 +183,35 @@
self.verify_repository_reviews( repository, reviewer=user, strings_displayed=strings_displayed )
def test_0040_review_readme( self ):
'''Review the readme component for the current tip revision.'''
+ """
+ We are at step 9.
+ Log in as test_user_2 and update the review with the readme component marked as 'Not applicable'.
+ # Review this revision:
+ # Data types (N/A)
+ # Functional tests (One star, comment 'functional tests missing')
+ # README (N/A)
+ """
self.logout()
self.login( email=common.test_user_2_email, username=common.test_user_2_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
user = test_db_util.get_user( common.test_user_2_email )
+ # The create_repository_review method takes a dict( component label=review contents ).
+ # If review_contents is empty, it marks that component as not applicable. The review
+ # contents dict should have the structure:
+ # {
+ # rating: 1-5,
+ # comment: <text>
+ # approved: yes/no
+ # private: yes/no
+ # }
review_contents_dict = { 'README': dict() }
self.review_repository( repository, review_contents_dict, user )
def test_0045_verify_readme_review( self ):
- '''Verify that the datatypes component review displays correctly.'''
+ '''Verify that the readme component review displays correctly.'''
+ """
+ We are at step 10.
+ Log in as test_user_1 and verify that the repository component reviews now include a review for the readme component.
+ """
self.logout()
self.login( email=common.test_user_1_email, username=common.test_user_1_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
@@ -116,14 +221,37 @@
self.verify_repository_reviews( repository, reviewer=user, strings_displayed=strings_displayed )
def test_0050_review_repository_dependencies( self ):
'''Review the repository dependencies component for the current tip revision.'''
+ """
+ We are at step 11.
+ Log in as test_user_2 and update the review with the repository dependencies component marked as 'Not applicable'.
+ # Review this revision:
+ # Data types (N/A)
+ # Functional tests (One star, comment 'functional tests missing')
+ # README (N/A)
+ # Repository dependencies (N/A)
+ """
self.logout()
self.login( email=common.test_user_2_email, username=common.test_user_2_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
user = test_db_util.get_user( common.test_user_2_email )
+ # The create_repository_review method takes a dict( component label=review contents ).
+ # If review_contents is empty, it marks that component as not applicable. The review
+ # contents dict should have the structure:
+ # {
+ # rating: 1-5,
+ # comment: <text>
+ # approved: yes/no
+ # private: yes/no
+ # }
review_contents_dict = { 'Repository dependencies': dict() }
self.review_repository( repository, review_contents_dict, user )
def test_0055_verify_repository_dependency_review( self ):
- '''Verify that the datatypes component review displays correctly.'''
+ '''Verify that the repository dependencies component review displays correctly.'''
+ """
+ We are at step 12.
+ Log in as test_user_1 and verify that the repository component reviews now include a review
+ for the repository dependencies component.
+ """
self.logout()
self.login( email=common.test_user_1_email, username=common.test_user_1_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
@@ -133,14 +261,38 @@
self.verify_repository_reviews( repository, reviewer=user, strings_displayed=strings_displayed )
def test_0060_review_tool_dependencies( self ):
'''Review the tool dependencies component for the current tip revision.'''
+ """
+ We are at step 13.
+ Log in as test_user_2 and update the review with the tool dependencies component marked as 'Not applicable'.
+ # Review this revision:
+ # Data types (N/A)
+ # Functional tests (One star, comment 'functional tests missing')
+ # README (N/A)
+ # Repository dependencies (N/A)
+ # Tool dependencies (N/A)
+ """
self.logout()
self.login( email=common.test_user_2_email, username=common.test_user_2_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
user = test_db_util.get_user( common.test_user_2_email )
+ # The create_repository_review method takes a dict( component label=review contents ).
+ # If review_contents is empty, it marks that component as not applicable. The review
+ # contents dict should have the structure:
+ # {
+ # rating: 1-5,
+ # comment: <text>
+ # approved: yes/no
+ # private: yes/no
+ # }
review_contents_dict = { 'Tool dependencies': dict() }
self.review_repository( repository, review_contents_dict, user )
def test_0065_verify_tool_dependency_review( self ):
- '''Verify that the datatypes component review displays correctly.'''
+ '''Verify that the tool dependencies component review displays correctly.'''
+ """
+ We are at step 14.
+ Log in as test_user_1 and verify that the repository component reviews now include a review
+ for the tool dependencies component.
+ """
self.logout()
self.login( email=common.test_user_1_email, username=common.test_user_1_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
@@ -150,14 +302,40 @@
self.verify_repository_reviews( repository, reviewer=user, strings_displayed=strings_displayed )
def test_0070_review_tools( self ):
'''Review the tools component for the current tip revision.'''
+ """
+ We are at step 15.
+ Log in as test_user_2 and update the review with the tools component given
+ a favorable review, with 5 stars, and approved status.
+ # Review this revision:
+ # Data types (N/A)
+ # Functional tests (One star, comment 'functional tests missing')
+ # README (N/A)
+ # Repository dependencies (N/A)
+ # Tool dependencies (N/A)
+ # Tools (5 stars, good review)
+ """
self.logout()
self.login( email=common.test_user_2_email, username=common.test_user_2_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
user = test_db_util.get_user( common.test_user_2_email )
+ # The create_repository_review method takes a dict( component label=review contents ).
+ # If review_contents is empty, it marks that component as not applicable. The review
+ # contents dict should have the structure:
+ # {
+ # rating: 1-5,
+ # comment: <text>
+ # approved: yes/no
+ # private: yes/no
+ # }
review_contents_dict = { 'Tools': dict( rating=5, comment='Excellent tool, easy to use.', approved='yes', private='no' ) }
self.review_repository( repository, review_contents_dict, test_db_util.get_user( common.test_user_2_email ) )
def test_0075_verify_tools_review( self ):
- '''Verify that the datatypes component review displays correctly.'''
+ '''Verify that the tools component review displays correctly.'''
+ """
+ We are at step 16.
+ Log in as test_user_1 and verify that the repository component reviews now include a review
+ for the tools component. As before, check for the presence of the comment on this review.
+ """
self.logout()
self.login( email=common.test_user_1_email, username=common.test_user_1_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
@@ -167,14 +345,40 @@
self.verify_repository_reviews( repository, reviewer=user, strings_displayed=strings_displayed )
def test_0080_review_workflows( self ):
'''Review the workflows component for the current tip revision.'''
+ """
+ We are at step 17.
+ Log in as test_user_2 and update the review with the workflows component marked as 'Not applicable'.
+ # Review this revision:
+ # Data types (N/A)
+ # Functional tests (One star, comment 'functional tests missing')
+ # README (N/A)
+ # Repository dependencies (N/A)
+ # Tool dependencies (N/A)
+ # Tools (5 stars, good review)
+ # Workflows (N/A)
+ """
self.logout()
self.login( email=common.test_user_2_email, username=common.test_user_2_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
user = test_db_util.get_user( common.test_user_2_email )
+ # The create_repository_review method takes a dict( component label=review contents ).
+ # If review_contents is empty, it marks that component as not applicable. The review
+ # contents dict should have the structure:
+ # {
+ # rating: 1-5,
+ # comment: <text>
+ # approved: yes/no
+ # private: yes/no
+ # }
review_contents_dict = { 'Workflows': dict() }
self.review_repository( repository, review_contents_dict, user )
def test_0085_verify_workflows_review( self ):
- '''Verify that the datatypes component review displays correctly.'''
+ '''Verify that the workflows component review displays correctly.'''
+ """
+ We are at step 18.
+ Log in as test_user_1 and verify that the repository component reviews now include a review
+ for the workflows component.
+ """
self.logout()
self.login( email=common.test_user_1_email, username=common.test_user_1_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
@@ -183,6 +387,11 @@
self.verify_repository_reviews( repository, reviewer=user, strings_displayed=strings_displayed )
def test_0090_upload_readme_file( self ):
'''Upload a readme file to the filtering repository.'''
+ """
+ We are at step 19.
+ Log in as test_user_1, the repository owner, and upload readme.txt to the repository. This will create
+ a new changeset revision for this repository, which will need to be reviewed.
+ """
self.logout()
self.login( email=common.test_user_1_email, username=common.test_user_1_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
@@ -190,21 +399,42 @@
self.upload_file( repository, 'readme.txt', commit_message="Uploaded readme.txt" )
def test_0095_review_new_changeset_readme_component( self ):
'''Update the filtering repository's readme component review to reflect the presence of the readme file.'''
+ """
+ We are at step 20.
+ There is now a new changeset revision in the repository's changelog, but it has no review associated with it.
+ Get the previously reviewed changeset hash, and pass that and the review id to the create_repository_review
+ method, in order to copy the previous review's contents. Then update the new review to reflect the presence of
+ a readme file.
+ """
self.logout()
self.login( email=common.test_user_2_email, username=common.test_user_2_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
user = test_db_util.get_user( common.test_user_2_email )
- # Get the changeset immediately prior to the tip, and pass it to the create review method.
- changelog = self.get_repository_changelog( repository )
- changeset_revision, ctx_revision = changelog[-2]
- previous_review = test_db_util.get_repository_review_by_user_id_changeset_revision( user.id, repository.id, str( changeset_revision ) )
+ # Get the last changeset revision that has a review associated with it.
+ last_review = self.get_last_reviewed_revision_by_user( user, repository )
+ if last_review is None:
+ raise AssertionError( 'Previous review expected, none found.' )
+ # The create_repository_review method takes a dict( component label=review contents ).
+ # If review_contents is empty, it marks that component as not applicable. The review
+ # contents dict should have the structure:
+ # {
+ # rating: 1-5,
+ # comment: <text>
+ # approved: yes/no
+ # private: yes/no
+ # }
review_contents_dict = { 'README': dict( rating=5, comment='Clear and concise readme file, a true pleasure to read.', approved='yes', private='no' ) }
self.create_repository_review( repository,
review_contents_dict,
changeset_revision=self.get_repository_tip( repository ),
- copy_from=( str( changeset_revision ), previous_review.id ) )
+ copy_from=( str( last_review.changeset_revision ), last_review.id ) )
def test_0100_verify_readme_review( self ):
'''Verify that the readme component review displays correctly.'''
+ """
+ We are at step 21.
+ Log in as the repository owner (test_user_1) and check the repository component reviews to
+ verify that the readme component is now reviewed and approved.
+ """
self.logout()
self.login( email=common.test_user_1_email, username=common.test_user_1_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
@@ -213,28 +443,51 @@
self.verify_repository_reviews( repository, reviewer=user, strings_displayed=strings_displayed )
def test_0105_upload_test_data( self ):
'''Upload the missing test data to the filtering repository.'''
- self.logout()
- self.login( email=common.test_user_1_email, username=common.test_user_1_name )
+ """
+ We are at step 22.
+ Remain logged in as test_user_1 and upload test data to the repository. This will also create a
+ new changeset revision that needs to be reviewed. This will replace the changeset hash associated with
+ the last dowloadable revision, but the last repository review will still be associated with the
+ last dowloadable revision hash.
+ """
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
user = test_db_util.get_user( common.test_user_2_email )
self.upload_file( repository, 'filtering/filtering_test_data.tar', commit_message="Uploaded test data." )
def test_0110_review_new_changeset_functional_tests( self ):
'''Update the filtering repository's readme component review to reflect the presence of the readme file.'''
+ """
+ We are at step 23.
+ Log in as test_user_2 and get the last reviewed changeset hash, and pass that and the review id to
+ the create_repository_review method, then update the copied review to approve the functional tests
+ component.
+ """
self.logout()
self.login( email=common.test_user_2_email, username=common.test_user_2_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
user = test_db_util.get_user( common.test_user_2_email )
# Get the changeset immediately prior to the tip, and pass it to the create review method.
- changelog = self.get_repository_changelog( repository )
- changeset_revision, ctx_revision = changelog[-2]
- previous_review = test_db_util.get_repository_review_by_user_id_changeset_revision( user.id, repository.id, str( changeset_revision ) )
+ last_review = self.get_last_reviewed_revision_by_user( user, repository )
+ # The create_repository_review method takes a dict( component label=review contents ).
+ # If review_contents is empty, it marks that component as not applicable. The review
+ # contents dict should have the structure:
+ # {
+ # rating: 1-5,
+ # comment: <text>
+ # approved: yes/no
+ # private: yes/no
+ # }
review_contents_dict = { 'Functional tests': dict( rating=5, comment='A good set of functional tests.', approved='yes', private='no' ) }
self.create_repository_review( repository,
review_contents_dict,
changeset_revision=self.get_repository_tip( repository ),
- copy_from=( str( changeset_revision ), previous_review.id ) )
+ copy_from=( str( last_review.changeset_revision ), last_review.id ) )
def test_0115_verify_functional_tests_review( self ):
'''Verify that the functional tests component review displays correctly.'''
+ """
+ We are at step 24.
+ Log in as the repository owner, test_user_1, and verify that the new revision's functional tests component
+ review has been updated with an approved status and favorable comment.
+ """
self.logout()
self.login( email=common.test_user_1_email, username=common.test_user_1_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
@@ -243,6 +496,11 @@
self.verify_repository_reviews( repository, reviewer=user, strings_displayed=strings_displayed )
def test_0120_upload_new_tool_version( self ):
'''Upload filtering 2.2.0 to the filtering repository.'''
+ """
+ We are at step 25.
+ Log in as test_user_1 and upload a new version of the tool to the filtering repository. This will create
+ a new downloadable revision, with no associated repository component reviews.
+ """
self.logout()
self.login( email=common.test_user_1_email, username=common.test_user_1_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
@@ -253,22 +511,38 @@
remove_repo_files_not_in_tar='No' )
def test_0125_review_new_changeset_functional_tests( self ):
'''Update the filtering repository's review to apply to the new changeset with filtering 2.2.0.'''
+ """
+ We are at step 26.
+ Log in as test_user_2 and copy the last review for this repository to the new changeset. Then
+ update the tools component review to refer to the new tool version.
+ """
self.logout()
self.login( email=common.test_user_2_email, username=common.test_user_2_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
user = test_db_util.get_user( common.test_user_2_email )
- # Get the changeset immediately prior to the tip, and pass it to the create review method.
- changelog = self.get_repository_changelog( repository )
- changeset_revision, ctx_revision = changelog[-2]
- previous_review = test_db_util.get_repository_review_by_user_id_changeset_revision( user.id, repository.id, str( changeset_revision ) )
+ last_review = self.get_last_reviewed_revision_by_user( user, repository )
# Something needs to change so that the review will save.
+ # The create_repository_review method takes a dict( component label=review contents ).
+ # If review_contents is empty, it marks that component as not applicable. The review
+ # contents dict should have the structure:
+ # {
+ # rating: 1-5,
+ # comment: <text>
+ # approved: yes/no
+ # private: yes/no
+ # }
review_contents_dict = { 'Tools': dict( rating=5, comment='Version 2.2.0 does the impossible and improves this tool.', approved='yes', private='yes' ) }
self.create_repository_review( repository,
review_contents_dict,
changeset_revision=self.get_repository_tip( repository ),
- copy_from=( str( changeset_revision ), previous_review.id ) )
+ copy_from=( str( last_review.changeset_revision ), last_review.id ) )
def test_0135_verify_review_for_new_version( self ):
'''Verify that the reviews display correctly for this changeset revision.'''
+ """
+ We are at step 27.
+ Log in as test_user_1 and check that the tools component review is for filtering 2.2.0, but that the other component
+ reviews had their contents copied from the last reviewed changeset.
+ """
self.logout()
self.login( email=common.test_user_1_email, username=common.test_user_1_name )
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
2 new commits in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/4b1a5865d413/
changeset: 4b1a5865d413
user: Kyle Ellrott
date: 2013-02-08 01:54:06
summary: Adding variable 'host_url' to provide qualified URL of host to tool help section.
affected #: 1 file
diff -r ce9789a35356da2b2ee4ae723506d5af57a0ce69 -r 4b1a5865d413005668b4a3c2c590c7dddb09205d templates/webapps/galaxy/tool_form.mako
--- a/templates/webapps/galaxy/tool_form.mako
+++ b/templates/webapps/galaxy/tool_form.mako
@@ -345,7 +345,7 @@
tool_help = tool.help
# Help is Mako template, so render using current static path.
- tool_help = tool_help.render( static_path=h.url_for( '/static' ) )
+ tool_help = tool_help.render( static_path=h.url_for( '/static' ), host_url=h.url_for('/', qualified=True) )
# Convert to unicode to display non-ascii characters.
if type( tool_help ) is not unicode:
https://bitbucket.org/galaxy/galaxy-central/commits/3eff396a81d8/
changeset: 3eff396a81d8
user: dannon
date: 2013-02-13 15:47:21
summary: Merged in kellrott/galaxy-central (pull request #119)
Adding variable 'host_url' to provide qualified URL of host to tool help section.
affected #: 1 file
diff -r 2f8989a1a16001e5dfcded4e0526ef3e2d6f55f9 -r 3eff396a81d833a2dcabce2fd34b53badf707984 templates/webapps/galaxy/tool_form.mako
--- a/templates/webapps/galaxy/tool_form.mako
+++ b/templates/webapps/galaxy/tool_form.mako
@@ -345,7 +345,7 @@
tool_help = tool.help
# Help is Mako template, so render using current static path.
- tool_help = tool_help.render( static_path=h.url_for( '/static' ) )
+ tool_help = tool_help.render( static_path=h.url_for( '/static' ), host_url=h.url_for('/', qualified=True) )
# Convert to unicode to display non-ascii characters.
if type( tool_help ) is not unicode:
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: jgoecks: Tool data tables: (a) add spaces between function per PEP8 and (b) add warning when an invalid line is found in tool data table.
by Bitbucket 12 Feb '13
by Bitbucket 12 Feb '13
12 Feb '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/2f8989a1a160/
changeset: 2f8989a1a160
user: jgoecks
date: 2013-02-13 00:44:06
summary: Tool data tables: (a) add spaces between function per PEP8 and (b) add warning when an invalid line is found in tool data table.
affected #: 1 file
diff -r 04b8060562f865fa5b3ec6dac4ecfe590bd3ec07 -r 2f8989a1a16001e5dfcded4e0526ef3e2d6f55f9 lib/galaxy/tools/data/__init__.py
--- a/lib/galaxy/tools/data/__init__.py
+++ b/lib/galaxy/tools/data/__init__.py
@@ -158,6 +158,7 @@
def __init__( self, config_element, tool_data_path ):
super( TabularToolDataTable, self ).__init__( config_element, tool_data_path )
self.configure_and_load( config_element, tool_data_path )
+
def configure_and_load( self, config_element, tool_data_path ):
"""
Configure and load table from an XML element.
@@ -196,11 +197,14 @@
self.missing_index_file = filename
log.warn( "Cannot find index file '%s' for tool data table '%s'" % ( filename, self.name ) )
self.data = all_rows
+
def handle_found_index_file( self, filename ):
self.missing_index_file = None
self.data.extend( self.parse_file_fields( open( filename ) ) )
+
def get_fields( self ):
return self.data
+
def parse_column_spec( self, config_element ):
"""
Parse column definitions, which can either be a set of 'column' elements
@@ -230,14 +234,17 @@
assert 'value' in self.columns, "Required 'value' column missing from column def"
if 'name' not in self.columns:
self.columns['name'] = self.columns['value']
+
def parse_file_fields( self, reader ):
"""
Parse separated lines from file and return a list of tuples.
TODO: Allow named access to fields using the column names.
"""
+ separator_char = (lambda c: '<TAB>' if c == '\t' else c)(self.separator)
+
rval = []
- for line in reader:
+ for i, line in enumerate( reader, start=1 ):
if line.lstrip().startswith( self.comment_char ):
continue
line = line.rstrip( "\n\r" )
@@ -245,6 +252,10 @@
fields = line.split( self.separator )
if self.largest_index < len( fields ):
rval.append( fields )
+ else:
+ log.warn( "Line %i in tool data table '%s' is invalid (HINT: "
+ "'%s' characters must be used to separate fields):\n%s"
+ % ( i, self.name, separator_char, line ) )
return rval
def get_entry( self, query_attr, query_val, return_attr ):
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: inithello: Tool shed functional tests for malformed XML in tool_dependencies.xml.
by Bitbucket 12 Feb '13
by Bitbucket 12 Feb '13
12 Feb '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/04b8060562f8/
changeset: 04b8060562f8
user: inithello
date: 2013-02-12 21:00:28
summary: Tool shed functional tests for malformed XML in tool_dependencies.xml.
affected #: 3 files
diff -r 01e73b11a46f87b03af29581603378b06187051d -r 04b8060562f865fa5b3ec6dac4ecfe590bd3ec07 test/tool_shed/functional/test_0010_repository_with_tool_dependencies.py
--- a/test/tool_shed/functional/test_0010_repository_with_tool_dependencies.py
+++ b/test/tool_shed/functional/test_0010_repository_with_tool_dependencies.py
@@ -63,20 +63,28 @@
'freebayes/sam_fa_indices.loc.sample',
strings_displayed=[],
commit_message='Uploaded tool data table .loc file.' )
- def test_0025_upload_invalid_tool_dependency_xml( self ):
+ def test_0025_upload_malformed_tool_dependency_xml( self ):
+ '''Upload tool_dependencies.xml with bad characters in the readme tag.'''
+ repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
+ self.upload_file( repository,
+ os.path.join( 'freebayes', 'malformed_tool_dependencies', 'tool_dependencies.xml' ),
+ valid_tools_only=False,
+ strings_displayed=[ 'Exception attempting to parse tool_dependencies.xml', 'not well-formed' ],
+ commit_message='Uploaded malformed tool dependency XML.' )
+ def test_0030_upload_invalid_tool_dependency_xml( self ):
'''Upload tool_dependencies.xml defining version 0.9.5 of the freebayes package.'''
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
self.upload_file( repository,
os.path.join( 'freebayes', 'invalid_tool_dependencies', 'tool_dependencies.xml' ),
strings_displayed=[ 'Name, version and type from a tool requirement tag does not match' ],
commit_message='Uploaded invalid tool dependency XML.' )
- def test_0030_upload_valid_tool_dependency_xml( self ):
+ def test_0035_upload_valid_tool_dependency_xml( self ):
'''Upload tool_dependencies.xml defining version 0.9.4_9696d0ce8a962f7bb61c4791be5ce44312b81cf8 of the freebayes package.'''
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
self.upload_file( repository,
os.path.join( 'freebayes', 'tool_dependencies.xml' ),
commit_message='Uploaded valid tool dependency XML.' )
- def test_0035_verify_tool_dependencies( self ):
+ def test_0040_verify_tool_dependencies( self ):
'''Verify that the uploaded tool_dependencies.xml specifies the correct package versions.'''
repository = test_db_util.get_repository_by_name_and_owner( repository_name, common.test_user_1_name )
self.display_manage_repository_page( repository,
diff -r 01e73b11a46f87b03af29581603378b06187051d -r 04b8060562f865fa5b3ec6dac4ecfe590bd3ec07 test/tool_shed/functional/test_1010_install_repository_with_tool_dependencies.py
--- a/test/tool_shed/functional/test_1010_install_repository_with_tool_dependencies.py
+++ b/test/tool_shed/functional/test_1010_install_repository_with_tool_dependencies.py
@@ -57,6 +57,11 @@
commit_message="Uploaded invalid_tool_dependencies/tool_dependencies.xml.",
remove_repo_files_not_in_tar='No' )
self.upload_file( repository,
+ os.path.join( 'freebayes', 'malformed_tool_dependencies', 'tool_dependencies.xml' ),
+ valid_tools_only=False,
+ strings_displayed=[ 'Exception attempting to parse tool_dependencies.xml', 'not well-formed' ],
+ commit_message='Uploaded malformed tool dependency XML.' )
+ self.upload_file( repository,
'freebayes/tool_dependencies.xml',
valid_tools_only=False,
commit_message="Uploaded tool_dependencies.xml",
diff -r 01e73b11a46f87b03af29581603378b06187051d -r 04b8060562f865fa5b3ec6dac4ecfe590bd3ec07 test/tool_shed/test_data/freebayes/malformed_tool_dependencies/tool_dependencies.xml
--- /dev/null
+++ b/test/tool_shed/test_data/freebayes/malformed_tool_dependencies/tool_dependencies.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+<tool_dependency>
+ <package name="freebayes" version="0.9.5">
+ <install version="1.0">
+ <actions>
+ <action type="shell_command">git clone --recursive git://github.com/ekg/freebayes.git</action>
+ <action type="shell_command">git reset --hard 9696d0ce8a962f7bb61c4791be5ce44312b81cf8</action>
+ <action type="shell_command">make</action>
+ <action type="move_directory_files">
+ <source_directory>bin</source_directory>
+ <destination_directory>$INSTALL_DIR/bin</destination_directory>
+ </action>
+ <action type="set_environment">
+ <environment_variable name="PATH" action="prepend_to">$INSTALL_DIR/bin</environment_variable>
+ </action>
+ </actions>
+ </install>
+ <readme>
+FreeBayes requires g++ and the standard C and C++ development libraries.
+Additionally, cmake is required for building the BamTools API.
+ </readme>
+ </package>
+ <package name="samtools" version="0.2.15">
+ <install version="1.0">
+ <actions>
+ <action type="download_by_url">http://sourceforge.net/projects/samtools/files/samtools/0.1.18/samtools-0.1…</action>
+ <action type="shell_command">sed -i .bak -e 's/-lcurses/-lncurses/g' Makefile</action>
+ <action type="shell_command">make</action>
+ <action type="move_file">
+ <source>samtools</source>
+ <destination>$INSTALL_DIR/bin</destination>
+ </action>
+ <action type="move_file">
+ <source>misc/maq2sam-long</source>
+ <destination>$INSTALL_DIR/bin</destination>
+ </action>
+ <action type="set_environment">
+ <environment_variable name="PATH" action="prepend_to">$INSTALL_DIR/bin</environment_variable>
+ </action>
+ </actions>
+ </install>
+ <readme>
+This readme tag has invalid XML ><
+ </readme>
+ </package>
+</tool_dependency>
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0
commit/galaxy-central: carlfeberhard: Incorporate headless browser testing using CasperJS (casperjs.org) into galaxy functional testing
by Bitbucket 12 Feb '13
by Bitbucket 12 Feb '13
12 Feb '13
1 new commit in galaxy-central:
https://bitbucket.org/galaxy/galaxy-central/commits/01e73b11a46f/
changeset: 01e73b11a46f
user: carlfeberhard
date: 2013-02-12 20:36:48
summary: Incorporate headless browser testing using CasperJS (casperjs.org) into galaxy functional testing
affected #: 7 files
diff -r ba8c49884f7daab5df8b62bd631157058c7ee910 -r 01e73b11a46f87b03af29581603378b06187051d test/casperjs/casperjs_runner.py
--- /dev/null
+++ b/test/casperjs/casperjs_runner.py
@@ -0,0 +1,324 @@
+"""Test runner for casperjs headless browser tests with the Galaxy distribution.
+
+Allows integration of casperjs tests with buildbot, run_functional_tests.sh
+
+Tests can be run in any of the following ways:
+* casperjs mytests.js --url='http://localhost:8080'
+* python casperjs_runner.py
+* nosetests
+* sh run_functional_tests.sh test/casperjs/test_runner
+* sh run_functional_tests.sh
+
+Note: that you can enable (lots) of debugging info using cli options:
+* casperjs usertests.js --url='http://localhost:8080' --verbose=true --logLevel=debug
+
+(see casperjs.org for more information)
+"""
+# -------------------------------------------------------------------- can't do 2.5
+import sys
+( major, minor, micro, releaselevel, serial ) = sys.version_info
+if minor < 6:
+ msg = 'casperjs requires python 2.6 or newer. Using: %s' %( sys.version )
+ try:
+ # if nose is installed do a skip test
+ from nose.plugins.skip import SkipTest
+ raise SkipTest( msg )
+ except ImportError, i_err:
+ raise AssertionError( msg )
+
+# --------------------------------------------------------------------
+import os
+import subprocess
+import json
+import errno
+import re
+
+import unittest
+from server_env import TestEnvironment
+
+import pprint
+import logging
+logging.basicConfig( stream=sys.stderr, name=__name__ )
+log = logging.getLogger( __name__ )
+
+# ==================================================================== MODULE VARS
+_PATH_TO_HEADLESS = 'casperjs'
+
+_TODO = """
+ get data back from js scripts (uploaded files, etc.)
+ use returned json to output list of failed assertions if code == 2
+"""
+
+# ====================================================================
+class HeadlessJSJavascriptError( Exception ):
+ """An error that occurrs in the javascript test file.
+ """
+ pass
+
+class CasperJSTestCase( unittest.TestCase ):
+ """Casper tests running in a unittest framework.
+ """
+ # casper uses a lot of escape codes to colorize output - these capture those and allow removal
+ escape_code_compiled_pattern = None
+ escape_code_pattern = r'\x1b\[[\d|;]+m'
+
+ # info on where to get casper js - shown when the exec can't be found
+ casper_info = """
+ CasperJS is a navigation scripting & testing utility for PhantomJS, written in Javascript.
+ More information is available at: casperjs.org
+ """
+
+ # debugging flag - set to true to have casperjs tests output with --verbose=true and --logLevel=debug
+ debug = False
+ # bit of a hack - this is the beginning of the last string when capserjs --verbose=true --logLevel=debug
+ # use this to get subprocess to stop waiting for output
+ casper_done_str = '[info] [phantom] Done'
+
+ # convert js test results to unittest.TestResults
+ results_adapter = None #CasperJsonToUnittestResultsConverter()
+
+ # ---------------------------------------------------------------- run the js script
+ def run_js_script( self, rel_script_path, *args, **kwargs ):
+ """Start the headless browser tests in a separate process and use both
+ the subprocess return code and the stdout output (formatted as JSON)
+ to determine which tests failed and which passed.
+ """
+ log.debug( 'beginning headless browser tests: %s', rel_script_path )
+ process_command_list = self.build_command_line( rel_script_path, *args, **kwargs )
+ log.debug( 'process_command_list: %s', str( process_command_list ) )
+ try:
+ process = subprocess.Popen( process_command_list, shell=False,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE )
+
+ # output from the browser (stderr only) immediately
+ while process.poll() == None:
+ stderr_msg = process.stderr.readline()
+ stderr_msg = self.strip_escape_codes( stderr_msg.strip() )
+ log.debug( '(%s): %s', rel_script_path, stderr_msg )
+ if stderr_msg.startswith( self.casper_done_str ):
+ break
+
+ # stdout is assumed to have the json test data/results
+ ( stdout_output, stderr_output ) = process.communicate()
+ #log.debug( '%s stdout output:\n%s', rel_script_path, stdout_output )
+ #log.debug( '%s stderr output:\n%s', rel_script_path, stderr_output )
+
+ log.debug( 'process.returncode: %d', process.returncode )
+ if process.returncode == 1:
+ #TODO: this is a fail on first effect
+ raise self.browser_error_to_exception( rel_script_path, stdout_output )
+
+ # couldn't find the headless browser,
+ # provide information (as it won't be included by default with galaxy)
+ except OSError, os_err:
+ if os_err.errno == errno.ENOENT:
+ log.error( 'No path to headless browser executable: %s\n'
+ + 'These tests were designed to use the following headless browser:\n%s',
+ self.exec_path, self.casper_info )
+ raise
+
+ self.handle_js_results( stdout_output )
+
+ def build_command_line( self, rel_script_path, *args, **kwargs ):
+ """Build the headless browser command line list for subprocess.
+ """
+ command_line_list = [ self.exec_path ]
+
+ # make rel_script_path an absolute path (when this is not run from it's dir - i.e. run_functional_tests.sh)
+ curr_dir = os.path.dirname( __file__ )
+ script_path = os.path.join( curr_dir, rel_script_path )
+ command_line_list.append( script_path )
+
+ # let browser know where the server is (from the TestEnvironment created in setUp)
+ command_line_list.append( '--url=' + self.env.url )
+
+ # add the return json only option
+ # - has script send normal output to stderr and results, errors, logs to stdout as json
+ command_line_list.append( '--return-json' )
+
+ # check flag to output (very) verbose debugging messages from casperjs and tests
+ if self.debug:
+ command_line_list.extend([ '--verbose=true', '--logLevel=debug' ])
+
+ #TODO: allow casperjs cli options ('--includes='), ?in args, kwargs?
+ command_line_list.extend( args )
+
+ # send extra data - encode kwargs as json to pass to casper for decoding
+ command_line_list.append( json.dumps( kwargs ) )
+ return command_line_list
+
+ def strip_escape_codes( self, msg ):
+ """Removes colorizing escape codes from casper output strings.
+ """
+ if not self.escape_code_compiled_pattern:
+ self.escape_code_compiled_pattern = re.compile( self.escape_code_pattern )
+ return re.sub( self.escape_code_compiled_pattern, '', msg )
+
+ # ---------------------------------------------------------------- convert js error to python error
+ def browser_error_to_exception( self, script_path, stdout_output ):
+ """Converts the headless' error from JSON into a more informative
+ python HeadlessJSJavascriptError.
+ """
+ get_error = lambda d: d[ 'errors' ][0]
+ get_msg = lambda err: err[ 'msg' ]
+ get_trace = lambda err: err[ 'backtrace' ]
+ try:
+ # assume it's json and located in errors (and first)
+ js_test_results = json.loads( stdout_output )
+ last_error = get_error( js_test_results )
+ err_string = ( "%s\n%s" %( get_msg( last_error ),
+ self.browser_backtrace_to_string( get_trace( last_error ) ) ) )
+
+ # if we couldn't parse json from what's returned on the error, raise a vanilla exc
+ except Exception, exc:
+ log.debug( '(failed to parse error returned from %s: %s)', _PATH_TO_HEADLESS, str( exc ) )
+ return HeadlessJSJavascriptError(
+ "ERROR in headless browser script %s" %( script_path ) )
+
+ # otherwise, raise with msg and backtrace
+ return HeadlessJSJavascriptError( err_string )
+
+ def browser_backtrace_to_string( self, backtrace ):
+ """Converts list of trace dictionaries (as might be returned from
+ json results) to a string similar to a python backtrace.
+ """
+ template = ' File "%s", line %s, in %s'
+ traces = []
+ for trace in backtrace:
+ traces.append( template %( trace[ 'file' ], trace[ 'line' ], trace[ 'function' ] ) )
+ return '\n'.join( traces )
+
+ # ---------------------------------------------------------------- results
+ def handle_js_results( self, results ):
+ """Handle the results of the js tests by either converting them
+ with the results adapter or checking for a failure list.
+ """
+ # if given an adapter - use it
+ if self.results_adapter:
+ self.results_adapter.convert( results, self )
+
+ # - otherwise, assert no failures found
+ else:
+ js_test_results = json.loads( results )
+ failures = js_test_results[ 'testResults' ][ 'failures' ]
+ assert len( failures ) == 0, (
+ "Some assertions failed in the headless browser tests (see the log for details)" )
+
+ # ---------------------------------------------------------------- TestCase overrides
+ def setUp( self ):
+ # set up the env for each test
+ self.env = TestEnvironment.instance()
+ self.exec_path = _PATH_TO_HEADLESS
+
+ def run( self, result=None ):
+ # wrap this in order to save ref to result
+ #TODO: gotta be a better way
+ self.result = result
+ unittest.TestCase.run( self, result=result )
+
+
+# ==================================================================== RESULTS CONVERSION
+class CasperJsonToUnittestResultsConverter( object ):
+ """Convert casper failures, success to individual unittest.TestResults
+ """
+ #TODO: So far I can add result instances - but each has the id, shortDescription
+ # of the TestCase.testMethod that called it. Can't find out how to change these.
+
+ def convert( self, json_results, test ):
+ """Converts JSON test results into unittest.TestResults.
+
+ precondition: test should have attribute 'result' which
+ is a unittest.TestResult (for that test).
+ """
+ results_dict = json.loads( json_results )
+ failures = results_dict[ 'testResults' ][ 'failures' ]
+ passes = results_dict[ 'testResults' ][ 'passes' ]
+ self.add_json_failures_to_results( failures, test )
+ self.add_json_successes_to_results( passes, test )
+
+ def add_json_failures_to_results( self, failures, test ):
+ """Converts JSON test failures.
+ """
+ #precondition: result should be an attr of test (a TestResult)
+ #TODO: no way to change test.desc, name in output?
+ for failure in failures:
+ #TODO: doesn't change shortDescription
+ #if 'standard' in failure:
+ # self.__doc__ = failure[ 'standard' ]
+ test.result.addFailure( test, self.casper_failure_to_unittest_failure( failure ) )
+ test.result.testsRun += 1
+
+ def casper_failure_to_unittest_failure( self, casper_failure, failure_class=AssertionError ):
+ """Returns a casper test failure (in dictionary form) as a 3-tuple of
+ the form used by unittest.TestResult.addFailure.
+
+ Used to add failures to a casperjs TestCase.
+ """
+ #TODO: this is all too elaborate
+ fail_type = casper_failure[ 'type' ]
+ values = json.dumps( casper_failure[ 'values' ] )
+ desc = casper_failure[ 'standard' ]
+ if 'messgae' in casper_failure:
+ desc = capser_failure[ 'message' ]
+ failure_msg = "(%s) %s: %s" %( fail_type, desc, values )
+ #TODO: tb is empty ([]) - can we get file info from casper, covert to py trace?
+ return ( failure_class, failure_msg, [] )
+
+ def add_json_successes_to_results( self, successes, test ):
+ """Converts JSON test successes.
+ """
+ for success in successes:
+ ## attempt to re-write test result description - doesn't work
+ #if 'standard' in success:
+ # self.__doc__ = success[ 'standard' ]
+ test.result.addSuccess( test )
+ test.result.testsRun += 1
+
+
+# ==================================================================== MODULE FIXTURE
+#NOTE: nose will run these automatically
+def setup_module():
+ log.debug( '\n--------------- setting up module' )
+
+def teardown_module():
+ log.debug( '\n--------------- tearing down module' )
+
+
+# ==================================================================== TESTCASE EXAMPLE
+# these could be broken out into other files - shouldn't be necc. ATM
+class UserTests( CasperJSTestCase ):
+ """TestCase that uses javascript and a headless browser to test dynamic pages.
+ """
+ def test_10_registration( self ):
+ """User registration tests: register new user, logout, attempt bad registrations.
+ """
+ # all keywords will be compiled into a single JSON obj and passed to the server
+ self.run_js_script( 'registration-tests.js', self.env.url,
+ testuser={ 'email': 'test1(a)test.test', 'password': '123456' })
+ #TODO:?? could theoretically do db cleanup, checks here with SQLALX
+ #TODO: have run_js_script return other persistant fixture data (uploaded files, etc.)
+
+ def test_20_login( self ):
+ """User log in tests.
+ """
+ self.run_js_script( 'login-tests.js', self.env.url,
+ testuser={ 'email': 'test1(a)test.test', 'password': '123456' })
+
+
+class ToolTests( CasperJSTestCase ):
+ """(Minimal) casperjs tests for tools.
+ """
+ #debug = True
+ def test_10_upload( self ):
+ """Tests uploading files
+ """
+ self.run_js_script( 'upload-tests.js' )
+
+
+# ==================================================================== MAIN
+if __name__ == '__main__':
+ log.setLevel( logging.DEBUG )
+ setup_module()
+ #TODO: server_env config doesn't work with unittest's lame main fn
+ unittest.main()
+ # teardown_module() isn't called when unittest.main is used
diff -r ba8c49884f7daab5df8b62bd631157058c7ee910 -r 01e73b11a46f87b03af29581603378b06187051d test/casperjs/login-tests.js
--- /dev/null
+++ b/test/casperjs/login-tests.js
@@ -0,0 +1,142 @@
+// have to handle errors here - or phantom/casper won't bail but _HANG_
+//TODO: global error handler?
+try {
+ var utils = require( 'utils' ),
+ xpath = require( 'casper' ).selectXPath,
+ format = utils.format,
+
+ //...if there's a better way - please let me know, universe
+ scriptDir = require( 'system' ).args[3]
+ // remove the script filename
+ .replace( /[\w|\.|\-|_]*$/, '' )
+ // if given rel. path, prepend the curr dir
+ .replace( /^(?!\/)/, './' ),
+ spaceghost = require( scriptDir + 'spaceghost' ).create({
+ // script options here (can be overridden by CLI)
+ //verbose: true,
+ //logLevel: debug,
+ scriptDir: scriptDir
+ });
+
+ spaceghost.start();
+
+} catch( error ){
+ console.debug( error );
+ phantom.exit( 1 );
+}
+
+// ===================================================================
+/* TODO:
+ move selectors and assertText strings into global object for easier editing
+
+
+*/
+// =================================================================== globals and helpers
+var email = spaceghost.getRandomEmail(),
+ password = '123456';
+if( spaceghost.fixtureData.testUser ){
+ email = spaceghost.fixtureData.testUser.email;
+ password = spaceghost.fixtureData.testUser.password;
+}
+
+// =================================================================== TESTS
+spaceghost.thenOpen( spaceghost.baseUrl, function(){
+ this.test.comment( 'loading galaxy homepage' );
+ // can we load galaxy?
+ this.test.assertTitle( 'Galaxy' );
+});
+
+// ------------------------------------------------------------------- should work
+
+// register a user (again...)
+spaceghost.then( function(){
+ this.test.comment( 'registering: ' + email );
+ spaceghost.registerUser( email, password );
+});
+// capture a sshot
+//spaceghost.then( function(){
+// this.clickLabel( 'User' );
+// this.capture( 'register.png' );
+//});
+
+// log them out - check for empty logged in text
+spaceghost.then( function(){
+ this.test.comment( 'logging out: ' + email );
+ spaceghost.logout();
+});
+spaceghost.then( function(){
+ this.test.assertSelectorDoesntHaveText(
+ xpath( '//a[contains(text(),"Logged in as")]/span["id=#user-email"]' ), /\w/ );
+ this.test.assert( spaceghost.loggedInAs() === '', 'loggedInAs() is empty string' );
+});
+
+// log them back in - check for email in logged in text
+spaceghost.then( function(){
+ this.test.comment( 'logging back in: ' + email );
+ spaceghost._submitLogin( email, password ); //No such user
+});
+spaceghost.then( function(){
+ this.test.assertSelectorHasText(
+ xpath( '//a[contains(text(),"Logged in as")]/span["id=#user-email"]' ), email );
+ this.test.assert( spaceghost.loggedInAs() === email, 'loggedInAs() matches email' );
+});
+
+// finally log back out for next tests
+spaceghost.then( function(){
+ this.test.comment( 'logging out: ' + email );
+ spaceghost.logout();
+});
+
+// ------------------------------------------------------------------- shouldn't work
+// can't log in: users that don't exist, bad emails, sql injection (hurhur)
+var badEmails = [ 'test2(a)test.org', 'test', '', "'; SELECT * FROM galaxy_user WHERE 'u' = 'u';" ];
+spaceghost.each( badEmails, function( self, badEmail ){
+ self.then( function(){
+ this.test.comment( 'attempting bad email: ' + badEmail );
+ this._submitLogin( badEmail, password );
+ });
+ self.then(function(){
+ this.assertErrorMessage( 'No such user' );
+ });
+});
+
+// can't use passwords that wouldn't be accepted in registration
+var badPasswords = [ '1234', '', '; SELECT * FROM galaxy_user' ];
+spaceghost.each( badPasswords, function( self, badPassword ){
+ self.then( function(){
+ this.test.comment( 'attempting bad password: ' + badPassword );
+ this._submitLogin( email, badPassword );
+ });
+ self.then(function(){
+ this.assertErrorMessage( 'Invalid password' );
+ });
+});
+
+// ------------------------------------------------------------------- test yoself
+// these versions are for conv. use in other tests, they should throw errors if used improperly
+spaceghost.then( function(){
+ this.assertStepsRaise( 'GalaxyError: LoginError', function(){
+ this.then( function(){
+ this.test.comment( 'testing (js) error thrown on bad email' );
+ this.login( 'nihilist', '1234' );
+ });
+ });
+});
+
+spaceghost.then( function(){
+ this.assertStepsRaise( 'GalaxyError: LoginError', function(){
+ this.then( function(){
+ this.test.comment( 'testing (js) error thrown on bad password' );
+ this.login( email, '1234' );
+ });
+ });
+});
+
+spaceghost.then( function(){
+ this.logout();
+});
+
+// ===================================================================
+spaceghost.run( function(){
+ this.test.done();
+});
diff -r ba8c49884f7daab5df8b62bd631157058c7ee910 -r 01e73b11a46f87b03af29581603378b06187051d test/casperjs/registration-tests.js
--- /dev/null
+++ b/test/casperjs/registration-tests.js
@@ -0,0 +1,182 @@
+// have to handle errors here - or phantom/casper won't bail but _HANG_
+try {
+ var utils = require( 'utils' ),
+ xpath = require( 'casper' ).selectXPath,
+ format = utils.format,
+
+ //...if there's a better way - please let me know, universe
+ scriptDir = require( 'system' ).args[3]
+ // remove the script filename
+ .replace( /[\w|\.|\-|_]*$/, '' )
+ // if given rel. path, prepend the curr dir
+ .replace( /^(?!\/)/, './' ),
+ spaceghost = require( scriptDir + 'spaceghost' ).create({
+ // script options here (can be overridden by CLI)
+ //verbose: true,
+ //logLevel: debug,
+ scriptDir: scriptDir
+ });
+
+ spaceghost.start();
+
+} catch( error ){
+ console.debug( error );
+ phantom.exit( 1 );
+}
+
+
+// ===================================================================
+/* TODO:
+ move selectors and assertText strings into global object for easier editing
+ pass email, etc. for first (successful) registration (for use with other tests)
+
+
+*/
+// =================================================================== globals and helpers
+var email = spaceghost.getRandomEmail(),
+ password = '123456',
+ confirm = password,
+ username = 'test' + Date.now();
+
+// =================================================================== TESTS
+spaceghost.thenOpen( spaceghost.baseUrl, function(){
+ this.test.comment( 'loading galaxy homepage' );
+ // can we load galaxy?
+ this.test.assertTitle( 'Galaxy' );
+ // xpath selector use:
+ this.test.assertExists( xpath( "//div[@id='masthead']" ), 'found masthead' );
+});
+
+// failing tests for...testing...the tests
+//spaceghost.thenOpen( spaceghost.baseUrl, function(){
+// this.test.comment( 'loading galaxy homepage' );
+// // can we load galaxy?
+// this.test.assertTitle( 'Blorgo' );
+// // xpath selector use:
+// this.test.assertExists( xpath( "//div[@id='facebook']" ), 'found facebook' );
+//});
+
+
+// ------------------------------------------------------------------- register a new user
+spaceghost.then( function(){
+ this.test.comment( 'registering user: ' + email );
+ this._submitUserRegistration( email, password, username, confirm );
+});
+spaceghost.thenOpen( spaceghost.baseUrl, function(){
+ this.clickLabel( 'User' );
+ this.test.assertSelectorHasText( 'a #user-email', email, '#user-email === ' + email );
+});
+
+
+// ------------------------------------------------------------------- log out that user
+spaceghost.then( function(){
+ this.test.comment( 'logging out user: ' + email );
+ this.logout();
+});
+spaceghost.then( function(){
+ this.debug( 'email:' + this.getElementInfo( 'a #user-email' ).html );
+ this.test.assert( !this.getElementInfo( 'a #user-email' ).html, '#user-email is empty' );
+});
+
+
+// ------------------------------------------------------------------- bad user registrations
+spaceghost.then( function(){
+ this.test.comment( 'attempting to re-register user: ' + email );
+ this._submitUserRegistration( email, password, username, confirm );
+});
+spaceghost.then(function(){
+ this.assertErrorMessage( 'User with that email already exists' );
+});
+
+// emails must be in the form -(a)-.- (which is an email on main, btw)
+var badEmails = [ 'bob', 'bob@', 'bob@idontwanttocleanup', 'bob.cantmakeme' ];
+spaceghost.each( badEmails, function( self, badEmail ){
+ self.then( function(){
+ this.test.comment( 'attempting bad email: ' + badEmail );
+ this._submitUserRegistration( badEmail, password, username, confirm );
+ });
+ self.then(function(){
+ this.assertErrorMessage( 'Enter a real email address' );
+ });
+});
+
+// passwords must be at least 6 chars long
+var badPasswords = [ '1234' ];
+spaceghost.each( badPasswords, function( self, badPassword ){
+ self.then( function(){
+ this.test.comment( 'attempting bad password: ' + badPassword );
+ this._submitUserRegistration( spaceghost.getRandomEmail(), badPassword, username, confirm );
+ });
+ self.then(function(){
+ this.assertErrorMessage( 'Use a password of at least 6 characters' );
+ });
+});
+
+// and confirm must match
+var badConfirms = [ '1234', '12345678', '123456 7', '' ];
+spaceghost.each( badConfirms, function( self, badConfirm ){
+ self.then( function(){
+ this.test.comment( 'attempting bad password confirmation: ' + badConfirm );
+ this._submitUserRegistration( spaceghost.getRandomEmail(), password, username, badConfirm );
+ });
+ self.then(function(){
+ this.assertErrorMessage( 'Passwords do not match' );
+ });
+});
+
+// usernames must be >=4 chars...
+//NOTE: that short username errors only show AFTER checking for existing/valid emails
+// so: we need to generate new emails for each one
+spaceghost.then( function(){
+ var newEmail = spaceghost.getRandomEmail(),
+ badUsername = 'bob';
+ this.test.comment( 'attempting short username: ' + badUsername );
+ this._submitUserRegistration( newEmail, password, badUsername, confirm );
+});
+spaceghost.then(function(){
+ this.assertErrorMessage( 'Public name must be at least 4 characters in length' );
+});
+
+// ...and be lower-case letters, numbers and '-'...
+var badUsernames = [ 'BOBERT', 'Robert Paulson', 'bobert!', 'bob_dobbs' ];
+spaceghost.each( badUsernames, function( self, badUsername ){
+ self.then( function(){
+ var newEmail = spaceghost.getRandomEmail();
+ this.test.comment( 'attempting bad username: ' + badUsername );
+ this._submitUserRegistration( newEmail, password, badUsername, confirm );
+ });
+ self.then(function(){
+ this.assertErrorMessage( "Public name must contain only lower-case letters, numbers and '-'" );
+ });
+});
+
+// ...and the name can't be used already
+spaceghost.then( function(){
+ var newEmail = spaceghost.getRandomEmail();
+ this.test.comment( 'attempting previously used username with new user: ' + newEmail );
+ this._submitUserRegistration( newEmail, password, username, confirm );
+});
+spaceghost.then(function(){
+ this.assertErrorMessage( 'Public name is taken; please choose another' );
+});
+
+
+// ------------------------------------------------------------------- test the tests
+// these versions are for conv. use in other tests, they should throw errors if used improperly
+spaceghost.then( function(){
+ this.assertStepsRaise( 'GalaxyError: RegistrationError', function(){
+ this.then( function(){
+ this.test.comment( 'testing (js) error thrown on bad email' );
+ this.registerUser( '@internet', '123456', 'ignobel' );
+ });
+ });
+});
+
+spaceghost.then( function(){
+ this.logout();
+});
+
+// ===================================================================
+spaceghost.run( function(){
+ this.test.done();
+});
diff -r ba8c49884f7daab5df8b62bd631157058c7ee910 -r 01e73b11a46f87b03af29581603378b06187051d test/casperjs/server_env.py
--- /dev/null
+++ b/test/casperjs/server_env.py
@@ -0,0 +1,97 @@
+"""
+Classes to handle fetching the proper environment and urls for the selenium
+tests to run against.
+"""
+
+import os
+import logging
+log = logging.getLogger( __name__ )
+
+class TestEnvironment( object ):
+ """Provides basic information on the server being tested.
+
+ Implemented as a singleton class so that it may persist between tests
+ without needing to be reset/re-created.
+ """
+ _instance = None
+
+ ENV_PROTOCOL = None
+ ENV_HOST = 'GALAXY_TEST_HOST'
+ ENV_PORT = 'GALAXY_TEST_PORT'
+ ENV_HISTORY_ID = 'GALAXY_TEST_HISTORY_ID'
+ ENV_FILE_DIR = 'GALAXY_TEST_FILE_DIR'
+ ENV_TOOL_SHED_TEST_FILE = 'GALAXY_TOOL_SHED_TEST_FILE'
+ ENV_SAVED_FILES_DIR = 'GALAXY_TEST_SAVE'
+
+ DEFAULT_PROTOCOL = 'http'
+ DEFAULT_HOST = 'localhost'
+ DEFAULT_PORT = '8080'
+
+ @classmethod
+ def instance( cls, config=None ):
+ # singleton pattern
+ if( ( not cls._instance )
+ or ( config ) ):
+ log.debug( 'creating singleton instance of "%s", config: %s', str( cls ), str( config ) )
+ cls._instance = cls( config )
+ return cls._instance
+
+ @classmethod
+ def get_server_url( cls ):
+ return cls.instance().url
+
+ def __init__( self, env_config_dict=None ):
+ self.config = env_config_dict or {}
+
+ self.protocol = self._get_setting_from_config_or_env( 'protocol', self.ENV_PROTOCOL, self.DEFAULT_PROTOCOL )
+ self.host = self._get_setting_from_config_or_env( 'host', self.ENV_HOST, self.DEFAULT_HOST )
+ self.port = self._get_setting_from_config_or_env( 'port', self.ENV_PORT, self.DEFAULT_PORT )
+
+ self.history_id = self._get_setting_from_config_or_env( 'history_id', self.ENV_HISTORY_ID, default=None )
+ self.file_dir = self._get_setting_from_config_or_env( 'file_dir', self.ENV_FILE_DIR, default=None )
+
+ self.tool_shed_test_file = self._get_setting_from_config_or_env(
+ 'tool_shed_test_file', self.ENV_TOOL_SHED_TEST_FILE, default=None )
+ self.shed_tools_dict = self._get_shed_tools_dict()
+
+ self.keepOutdir = self._get_setting_from_config_or_env( 'keepOutdir', self.ENV_SAVED_FILES_DIR, default=None )
+ self._init_saved_files_dir()
+
+ def _get_setting_from_config_or_env( self, config_name, env_name, default=False ):
+ """Try to get a setting from (in order):
+ TestEnvironment.config, the os env, or some default (if not False).
+ """
+ config = self.config.get( config_name, None )
+ env = os.environ.get( env_name, None )
+ if( ( not ( config or env ) )
+ and ( default == False ) ):
+ raise AttributeError( '"%s" was not set via config or %s or default' %( config_name, env_name ) )
+ return config or env or default
+
+ def _get_shed_tools_dict( self ):
+ """Read the shed tools from the tool shed test file if given,
+ otherwise an empty dict.
+ """
+ if self.tool_shed_test_file:
+ f = open( self.tool_shed_test_file, 'r' )
+ text = f.read()
+ f.close()
+ return from_json_string( text )
+ else:
+ return {}
+
+ def _init_saved_files_dir( self ):
+ """Set up the desired directory to save test output
+ """
+ if self.keepOutdir > '':
+ try:
+ os.makedirs( self.keepOutdir )
+ except:
+ log.debug( 'unable to create saved files directory: %s' %( self.keepOutDir ) )
+
+ @property
+ def url( self ):
+ url = '%s://%s' %( self.protocol, self.host )
+ if self.port and self.port != 80:
+ url += ':%s' %( str( self.port ) )
+ return url
diff -r ba8c49884f7daab5df8b62bd631157058c7ee910 -r 01e73b11a46f87b03af29581603378b06187051d test/casperjs/spaceghost.js
--- /dev/null
+++ b/test/casperjs/spaceghost.js
@@ -0,0 +1,1030 @@
+/* TODO:
+ Use in test command
+
+ bug: assertStepsRaise raise errors (all the way) when used in 'casperjs test .'
+
+ Does it run:
+ casperjs usertests.js --url='http://localhost:8080'
+ casperjs usertests.js --url='http://localhost:8080' --return-json
+ casperjs usertests.js --url='http://localhost:8080' --verbose=true --logLevel=debug
+ casperjs test test/casperjs --url='http://localhost:8080'
+ python casperjs_runner.py
+ nosetests
+ sh run_functional_tests.sh test/casperjs/
+ sh run_functional_tests.sh
+ (buildbot)
+
+ BUGS:
+ echo doesn't seem to work with python
+ trace not showing for errors here
+
+ what if:
+ does an error saving a sshot bail the entire suite?
+
+ Do the above handle:
+ test script errors
+ page errors (evaluate, find element, etc.)
+ failures
+ passes
+ python errors
+
+ Does test_runner:
+ aggregate properly (passes, failures)
+ fail on first = false
+
+ Test:
+ screenshotting
+ save html/sshots to GALAXY_TEST_SAVE (test_runner)
+
+ can we pass the entire test_env (instead of just url) from test_runner to sg?
+ support method chaining pattern
+ move selectors, text to class level (spaceghost)
+
+ modules?
+ May want to move common functions into PageObject-like subs of sg, e.g.:
+ spaceghost.loginPage.logout()
+ spaceghost.masthead.userMenu().login() // to click User -> Login
+
+ more conv. functions:
+ withMainFrame( callback )
+ getMessageInfo returns *message elementInfo or null
+
+ frames in casper are a PITA (as are steps in gen.): is there a better way to select within a frame w/o a step?
+ waitFor (with progress and finally): a gen. form of waitForHdaState
+
+*/
+// ===================================================================
+/** Extended version of casper object for use with Galaxy
+ */
+
+// ------------------------------------------------------------------- modules
+var Casper = require( 'casper' ).Casper;
+var utils = require( 'utils' );
+
+// ------------------------------------------------------------------- inheritance
+/**
+ */
+function SpaceGhost(){
+ SpaceGhost.super_.apply( this, arguments );
+ this.init.apply( this, arguments );
+}
+utils.inherits( SpaceGhost, Casper );
+
+//console.debug( 'CasperError:' + CasperError );
+
+// ------------------------------------------------------------------- error types
+PageError.prototype = new CasperError();
+PageError.prototype.constructor = CasperError;
+function PageError(){
+ CasperError.apply( this, arguments );
+ this.name = "PageError";
+}
+
+GalaxyError.prototype = new CasperError();
+GalaxyError.prototype.constructor = CasperError;
+function GalaxyError(){
+ CasperError.apply( this, arguments );
+ this.name = "GalaxyError";
+}
+
+AlertError.prototype = new CasperError();
+AlertError.prototype.constructor = CasperError;
+function AlertError(){
+ CasperError.apply( this, arguments );
+ this.name = "AlertError";
+}
+
+// =================================================================== METHODS / OVERRIDES
+// ------------------------------------------------------------------- set up
+/** More initialization: cli, event handlers, etc.
+ * @param {Object} options option hash
+ */
+SpaceGhost.prototype.init = function init( options ){
+ //console.debug( 'init, options:', JSON.stringify( options, null, 2 ) );
+
+ //NOTE: cli will override in-script options
+ this._setOptionsFromCli();
+
+ // save errors for later output (needs to go before process CLI)
+ this.errors = [];
+ this.on( 'error', function( msg, backtrace ){
+ //this.debug( 'adding error to stack: ' + msg + ', trace:' + JSON.stringify( backtrace, null, 2 ) );
+ this.errors.push({ msg: msg, backtrace: backtrace });
+ });
+ this._processCLIArguments();
+ this._setUpEventHandlers();
+
+ // inject these scripts by default
+ this.debug( 'this.options.scriptDir:' + this.options.scriptDir );
+ this.options.clientScripts = [
+ this.options.scriptDir + '../../static/scripts/libs/jquery/jquery.js'
+ //...
+ ].concat( this.options.clientScripts );
+ this.debug( 'clientScripts:\n' + this.jsonStr( this.options.clientScripts ) );
+
+};
+
+/** Allow CLI arguments to set options if the proper option name is used.
+ * @example:
+ * casperjs myscript.js --verbose=true --logLevel=debug
+ */
+SpaceGhost.prototype._setOptionsFromCli = function setOptionsFromCli(){
+ // get and remove any casper options passed on the command line
+ for( var optionName in this.options ){
+ if( this.cli.has( optionName ) ){
+ //console.debug( optionName + ':' + this.options[ optionName ] + ',' + this.cli.get( optionName ) );
+ this.options[ optionName ] = this.cli.get( optionName );
+ this.cli.drop( optionName );
+ }
+ }
+};
+
+// ------------------------------------------------------------------- cli args and options
+SpaceGhost.prototype._saveHtmlOnErrorHandler = function _saveHtmlOnErrorHandler( msg, backtrace ){
+ // needs to output to a file in GALAXY_SAVE
+ //this.debugHTML();
+};
+
+SpaceGhost.prototype._saveTextOnErrorHandler = function _saveTextOnErrorHandler( msg, backtrace ){
+ // needs to output to a file in GALAXY_SAVE
+ //this.debugPage();
+};
+
+SpaceGhost.prototype._saveScreenOnErrorHandler = function _saveScreenOnErrorHandler( msg, backtrace ){
+ // needs to output to a pic in GALAXY_SAVE
+ //var filename = ...??
+ //?? this.getCurrentUrl(), this.getCurrent
+ //this.capture( filename );
+};
+
+
+/** Set up any SG specific options passed in on the cli.
+ */
+SpaceGhost.prototype._processCLIArguments = function _processCLIArguments(){
+ //TODO: init these programmitically
+ var CLI_OPTIONS = {
+ returnJsonOnly : { defaultsTo: false, flag: 'return-json', help: 'send output to stderr, json to stdout' },
+ raisePageError : { defaultsTo: true, flag: 'page-error', help: 'raise errors thrown on the page' },
+ errorOnAlert : { defaultsTo: false, flag: 'error-on-alert', help: 'throw errors when a page calls alert' },
+ failOnAlert : { defaultsTo: true, flag: 'fail-on-alert', help: 'fail a test when a page calls alert' }
+ //screenOnError : { defaultsTo: false, flag: 'error-screen', help: 'capture a screenshot on a page error' },
+ //textOnError : { defaultsTo: false, flag: 'error-text', help: 'output page text on a page error' },
+ //htmlOnError : { defaultsTo: false, flag: 'error-html', help: 'output page html on a page error' }
+ };
+
+ // --url parameter required (the url of the server to test with)
+ if( !this.cli.has( 'url' ) ){
+ this.die( 'Test server URL is required - ' +
+ 'Usage: capserjs <test_script.js> --url=<test_server_url>', 1 );
+ }
+ this.baseUrl = this.cli.get( 'url' );
+
+ // --return-json: supress all output except for JSON logs, test results, and errors at finish
+ // this switch allows a testing suite to send JSON data back via stdout (w/o logs, echos interferring)
+ this.options.returnJsonOnly = CLI_OPTIONS.returnJsonOnly.defaultsTo;
+ if( this.cli.has( CLI_OPTIONS.returnJsonOnly.flag ) ){
+ this.options.returnJsonOnly = true;
+
+ //this._suppressOutput();
+ this._redirectOutputToStderr();
+
+ // output json on fail-first error
+ this.on( 'error', function( msg, backtrace ){
+ //console.debug( 'return-json caught error' );
+ if( spaceghost.options.exitOnError ){
+ this.outputStateAsJson();
+ spaceghost.exit( 1 );
+ }
+ });
+ // non-error finshes/json-output are handled in run() for now
+ }
+
+ // --error-on-alert=false: don't throw an error if the page calls alert (default: true)
+ this.options.raisePageError = CLI_OPTIONS.raisePageError.defaultsTo;
+ if( this.cli.has( CLI_OPTIONS.raisePageError.flag ) ){
+ this.options.raisePageError = this.cli.get( CLI_OPTIONS.raisePageError.flag );
+ }
+
+ // --error-on-alert=false: don't throw an error if the page calls alert (default: true)
+ this.options.errorOnAlert = CLI_OPTIONS.errorOnAlert.defaultsTo;
+ if( this.cli.has( CLI_OPTIONS.errorOnAlert.flag ) ){
+ this.options.errorOnAlert = this.cli.get( CLI_OPTIONS.errorOnAlert.flag );
+ }
+
+ // --fail-on-alert=false: don't fail a test if the page calls alert (default: true)
+ this.options.failOnAlert = CLI_OPTIONS.failOnAlert.defaultsTo;
+ if( this.cli.has( CLI_OPTIONS.failOnAlert.flag ) ){
+ this.options.failOnAlert = this.cli.get( CLI_OPTIONS.failOnAlert.flag );
+ }
+
+ /* not implemented
+ // --error-page: print the casper.debugPage (the page's text) output on an error
+ if( this.cli.has( 'error-page' ) ){
+ this.on( 'page.error', this._saveTextOnErrorHandler );
+
+ // --error-html: print the casper.debugHTML (the page's html) output on an error (mut.exc w error-text)
+ } else if( this.cli.has( 'error-html' ) ){
+ this.on( 'page.error', this._saveHtmlOnErrorHandler );
+ }
+
+ // --error-screen: print the casper.debugPage (the page's text) output on an error
+ if( this.cli.has( 'error-screen' ) ){
+ this.on( 'page.error', this._saveScreenOnErrorHandler );
+ }
+ */
+
+ // get any fixture data passed in as JSON in args
+ // (NOTE: currently the 2nd arg (with the url being 1st?)
+ this.fixtureData = ( this.cli.has( 1 ) )?( JSON.parse( this.cli.get( 1 ) ) ):( {} );
+ this.debug( 'fixtureData:' + this.jsonStr( this.fixtureData ) );
+
+};
+
+/** Suppress the normal output from the casper object (echo, errors)
+ */
+SpaceGhost.prototype._suppressOutput = function _suppressOutput(){
+ // currently (1.0) the only way to suppress test pass/fail messages
+ // (no way to re-route to log either - circular)
+ this.echo = function( msg ){};
+
+ //this.removeListener( 'error', this.listeners( 'error' )[0] );
+ // clear the casper listener that outputs formatted error messages
+ this.removeListener( 'error', this.listeners( 'error' )[1] );
+};
+
+/** Suppress the normal output from the casper object (echo, errors)
+ */
+SpaceGhost.prototype._redirectOutputToStderr = function _redirectOutputToStderr(){
+ // currently (1.0) the only way to suppress test pass/fail messages
+ // (no way to re-route to log either - circular)
+ var spaceghost = this;
+ this.echo = function( msg ){
+ spaceghost.stderr( msg );
+ };
+
+ //this.removeListener( 'error', this.listeners( 'error' )[0] );
+ // clear the casper listener that outputs formatted error messages
+ this.removeListener( 'error', this.listeners( 'error' )[1] );
+};
+
+/** Outputs logs, test results and errors in a single JSON formatted object.
+ */
+SpaceGhost.prototype.outputStateAsJson = function outputStateAsJson(){
+ var returnedJSON = {
+ logs: this.result,
+ testResults: this.test.testResults,
+ errors: this.errors
+ };
+ // use phantomjs console since echo can't be used (suppressed - see init)
+ console.debug( JSON.stringify( returnedJSON, null, 2 ) );
+};
+
+
+// ------------------------------------------------------------------- event handling
+//note: using non-anon fns to allow removal if needed
+// most of these are stubs (w logging) for later expansion
+
+/** Event handler for failed page loads
+ */
+SpaceGhost.prototype._loadFailedHandler = function _loadFailedHandler( object ){
+ this.error( 'load.failed: ' + spaceghost.jsonStr( object ) );
+ //TODO: throw error?
+};
+
+/** Event handler for page errors (js) - throws test scope as PageError
+ * NOTE: this has some special handling for DOM exc 12 which some casper selectors are throwing
+ * (even tho the selector still works)
+ */
+SpaceGhost.prototype._pageErrorHandler = function _pageErrorHandler( msg, backtrace ){
+ // add a page error handler to catch page errors (what we're most interested with here)
+ // normally, casper seems to let these pass unhandled
+ //console.debug( 'page.error:' + msg );
+
+ //TODO:!! lots of casper selectors are throwing this - even tho they still work
+ if( msg === 'SYNTAX_ERR: DOM Exception 12: An invalid or illegal string was specified.' ){
+ void( 0 ); // no op
+
+ } else if( this.options.raisePageError ){
+ //console.debug( '(page) Error: ' + msg );
+ //this.bypassOnError = true;
+
+ // ugh - these bounce back and forth between here and phantom.page.onError
+ // if we don't do this replace you end up with 'PageError: PageError: PageError: ...'
+ // I haven't found a great way to prevent the bouncing
+ msg = msg.replace( 'PageError: ', '' );
+ throw new PageError( msg, backtrace );
+ }
+};
+
+/** Event handler for console messages from the page.
+ */
+SpaceGhost.prototype._pageConsoleHandler = function _pageConsoleHandler(){
+ // remote.message
+ var DELIM = '-';
+ this.debug( this + '(page console) "' + Array.prototype.join.call( arguments, DELIM ) + '"' );
+};
+
+/** Event handler for alerts
+ */
+SpaceGhost.prototype._alertHandler = function _alertHandler( message ){
+ // casper info level already has outputs these
+ //this.warning( this + '(page alert)\n"' + message + '"' );
+ var ALERT_MARKER = '(page alert) ';
+
+ // either throw an error or fail the test
+ //console.debug( 'this.options.errorOnAlert: ' + this.options.errorOnAlert );
+ this.stderr( 'this.options.failOnAlert: ' + this.options.failOnAlert );
+ if( this.options.errorOnAlert ){
+ throw new PageError( ALERT_MARKER + message );
+
+ } else if( this.options.failOnAlert ){
+ //this.test.fail( ALERT_MARKER + message );
+ //this.test.fail();
+ this.test.assert( false, 'found alert message' );
+ //this.stderr( 'this.options.failOnAlert: ' + this.options.failOnAlert );
+ }
+};
+
+/** Event handler for navigation requested (loading of frames, redirects(?))
+ */
+SpaceGhost.prototype._navHandler = function _navHandler( url, navigationType, navigationLocked, isMainFrame ){
+ this.debug( 'navigation.requested: ' + url );
+};
+
+/** Set up event handlers.
+ */
+SpaceGhost.prototype._setUpEventHandlers = function _setUpEventHandlers(){
+ //console.debug( '_setUpEventHandlers' );
+
+ // ........................ page errors
+ this.on( 'page.error', this._pageErrorHandler );
+ //this.on( 'load.failed', this._loadFailedHandler );
+
+ // ........................ page info/debugging
+ // these are already displayed at the casper info level
+
+ //this.on( 'remote.message', this._pageConsoleHandler );
+ this.on( 'remote.alert', this._alertHandler );
+
+ // these are already displayed at the casper debug level
+ //this.on( 'navigation.requested', this._navHandler );
+
+};
+
+// ------------------------------------------------------------------- page control
+/** An override of casper.open specifically for Galaxy.
+ * (Currently only used to change language headers)
+ */
+SpaceGhost.prototype.open = function open(){
+ //TODO: this can be moved to start (I think...?)
+ //!! override bc phantom has it's lang as 'en-US,*' and galaxy doesn't handle the '*' well (server error)
+ this.page.customHeaders = { 'Accept-Language': 'en-US' };
+ return Casper.prototype.open.apply( this, arguments );
+};
+
+/** An override to provide json output and more informative error codes
+ */
+SpaceGhost.prototype.run = function run( onComplete, time ){
+ // wrap the onComplete to:
+ // return code 2 on test failure
+ // 0 on success
+ // (1 on js error - in error handler)
+ var new_onComplete = function(){
+ onComplete.call( this );
+ var returnCode = ( this.test.testResults.failed )?( 2 ):( 0 );
+
+ // if --return-json is used: output json and exit
+ if( this.options.returnJsonOnly ){
+ this.outputStateAsJson();
+ this.exit( returnCode );
+
+ // otherwise, render the nice casper output and exit
+ } else {
+ this.test.renderResults( true, returnCode );
+ }
+ };
+ Casper.prototype.run.call( this, new_onComplete, time );
+};
+
+/** Install a function as an error handler temporarily, run a function with steps, then remove the handler.
+ * A rough stand-in for try catch with steps.
+ * CatchFn will be passed error's msg and trace.
+ * @param {Function} stepsFn a function that puts casper steps on the stack (then, thenOpen, etc.)
+ * @param {Function} catchFn some portion of the correct error msg
+ */
+SpaceGhost.prototype.tryStepsCatch = function tryStepsCatch( stepsFn, catchFn ){
+ //TODO: * @param {Boolean} removeOtherListeners option to remove other listeners while this fires
+ // create three steps: 1) set up new error handler, 2) try the fn, 3) check for errors and rem. handler
+ var originalExitOnError,
+ errorMsg = '', errorTrace = [],
+ recordError = function( msg, trace ){
+ errorMsg = msg; errorTrace = trace;
+ };
+
+ // dont bail on the error (but preserve option), install hndlr to simply record msg, trace
+ //NOTE: haven't had to remove other listeners yet
+ this.then( function(){
+ originalExitOnError = this.options.exitOnError;
+ this.options.exitOnError = false;
+ this.on( 'error', recordError );
+ });
+
+ // try the step...
+ this.then( stepsFn );
+
+ this.then( function(){
+ // ...and if an error was recorded call the catch with the info
+ if( errorMsg ){
+ catchFn.call( this, errorMsg, errorTrace );
+ }
+ // remove that listener either way and restore the bail option
+ this.removeListener( 'error', recordError );
+ this.options.exitOnError = originalExitOnError;
+ });
+};
+
+
+// =================================================================== TESTING
+//TODO: form fill doesn't work as casperjs would want it - often a button -> controller url
+//TODO: saveScreenshot (to GALAXY_TEST_SAVE)
+//TODO: saveHtml (to GALAXY_TEST_SAVE)
+
+/** Casper has an (undocumented?) skip test feature. This is a conv. wrapper for that.
+ */
+SpaceGhost.prototype.skipTest = function(){
+ //TODO: does this work? seems to...
+ throw this.test.SKIP_MESSAGE;
+};
+
+/** test helper - within frame, assert selector, and assert text in selector
+ * @param {CasperJS selector} selector what element in which to search for the text
+ * @param {String} text what text to search for
+ * @param {String} frame frame selector (gen. name) in which to search for selector (defaults to top)
+ */
+SpaceGhost.prototype.assertSelectorAndTextInFrame = function assertSelectorAndTextInFrame( selector, text, frame ){
+ var spaceghost = this;
+ function assertSelectorAndText( selector, text ){
+ spaceghost.test.assertExists( selector,
+ format( "found '%s' in %s", selector, frame ) );
+ spaceghost.test.assertSelectorHasText( selector, text,
+ format( "%s contains '%s'", selector, text ) );
+ }
+ if( frame ){
+ this.withFrame( frame, function(){
+ assertSelectorAndText( selector, text );
+ });
+ } else {
+ assertSelectorAndText( selector, text );
+ }
+}
+
+/** test helper - within frame, assert errormessage, and assert text in errormessage
+ * *message is a common UI feedback motif in Galaxy (often displayed in the main panel)
+ * @param {String} message what the message should contain
+ * @param {String} frame frame selector (gen. name) in which to search for selector (defaults to 'galaxy_main')
+ * @param {CasperJS selector} messageSelector what element in which to search for the text (defaults to '.errormessage')
+ */
+SpaceGhost.prototype.assertErrorMessage = function assertSelectorAndTextInFrame( message, frame, messageSelector ){
+ messageSelector = messageSelector || this.selectors.messages.error;
+ frame = frame || this.selectors.frames.main;
+ this.assertSelectorAndTextInFrame( messageSelector, message, frame );
+};
+
+/** Assert that stepsFn (which contains casper.then or some other casper step function) raises an error with
+ * a msg that contains some text (msgContains).
+ * @param {String} msgContains some portion of the correct error msg
+ * @param {Function} stepsFn a function that puts casper steps on the stack (then, thenOpen, etc.)
+ */
+SpaceGhost.prototype.assertStepsRaise = function assertStepsRaise( msgContains, stepsFn, removeOtherListeners ){
+ // casper provides an assertRaises but this doesn't work well with steps
+ //TODO: * @param {Boolean} removeOtherListeners option to remove other listeners while this fires
+ var spaceghost = this;
+ function testTheError( msg, backtrace ){
+ spaceghost.test.assert( msg.indexOf( msgContains ) != -1, 'Raised correct error: ' + msg );
+ }
+ this.tryStepsCatch( stepsFn, testTheError );
+};
+
+// =================================================================== CONVENIENCE
+/** Wraps casper.getElementInfo in try, returning null if element not found instead of erroring.
+ * @param {String} selector css or xpath selector for the element to find
+ */
+SpaceGhost.prototype.elementInfoOrNull = function elementInfoOrNull( selector ){
+ var found = null;
+ try {
+ found = this.getElementInfo( selector );
+ } catch( err ){}
+ return found;
+};
+
+/** Wraps casper.click in try, returning true if element found and clicked, false if not instead of erroring.
+ * @param {String} selector css or xpath selector for the element to find
+ */
+SpaceGhost.prototype.tryClick = function tryClick( selector ){
+ var done = false;
+ try {
+ found = this.click( selector );
+ done = true;
+ } catch( err ){}
+ return done;
+};
+
+
+// =================================================================== GALAXY CONVENIENCE
+/** Gets a psuedo-random (unique?) email based on the time stamp.
+ * Helpful for testing registration.
+ * @param {String} username email user (defaults to 'test')
+ * @param {String} domain email domain (defaults to 'test.test')
+ */
+SpaceGhost.prototype.getRandomEmail = function getRandomEmail( username, domain ){
+ username = username || 'test';
+ domain = domain || 'test.test';
+ return username + Date.now() + '@' + domain;
+};
+
+
+/** Tests registering a new user on the Galaxy instance by submitting the registration form.
+ * NOTE: this version does NOT throw an error on a bad registration.
+ * It is meant for testing the registration functionality and, therefore, is marked as private.
+ * Other tests should use registerUser
+ * @param {String} email the users email address
+ * @param {String} password the users password
+ * @param {String} username the users ...username! (optional: will use 1st part of email)
+ * @param {String} confirm password confirmation (optional: defaults to password)
+ */
+SpaceGhost.prototype._submitUserRegistration = function _submitUserRegistration( email, password, username, confirm ){
+ var userInfo = {
+ email : email,
+ password: password,
+ // default username to first part of email
+ username:( !username && email.match( /^\w*/ ) )?( email.match( /^\w*/ ) ):( username ),
+ // default confirm duplicate of password
+ confirm : ( confirm !== undefined )?( confirm ):( password )
+ };
+ this.debug( 'registering user:\n' + this.jsonStr( userInfo ) );
+
+ this.thenOpen( this.baseUrl, function(){
+
+ this.clickLabel( this.labels.masthead.menus.user );
+ this.clickLabel( this.labels.masthead.userMenu.register );
+
+ this.withFrame( this.selectors.frames.main, function mainBeforeRegister(){
+ this.debug( 'submitting registration... ' + this.getCurrentUrl() );
+ this.fill( this.selectors.registrationPage.form, userInfo, false );
+ // need manual up
+ this.click( xpath( this.selectors.registrationPage.submit_xpath ) );
+ });
+
+ this.withFrame( this.selectors.frames.main, function mainAfterRegister(){
+ var messageInfo = this.getElementInfo( this.selectors.messages.all );
+ this.debug( 'post registration message:\n' + this.jsonStr( messageInfo ) );
+ });
+ });
+};
+
+/** Register a new user on the Galaxy instance.
+ * @param {String} email the users email address
+ * @param {String} password the users password
+ * @param {String} username the users ...username! (optional: will use 1st part of email)
+ */
+SpaceGhost.prototype.registerUser = function registerUser( email, password, username ){
+ this._submitUserRegistration( email, password, username );
+ this.then( function(){
+ this.withFrame( this.selectors.frames.main, function mainAfterRegister(){
+ var messageInfo = this.getElementInfo( this.selectors.messages.all );
+ this.debug( 'post registration message:\n' + this.jsonStr( messageInfo ) );
+
+ if( messageInfo.attributes[ 'class' ] === 'errormessage' ){
+ throw new GalaxyError( 'RegistrationError: ' + messageInfo.html );
+ }
+ });
+ });
+ return this;
+};
+
+/** Log out the current user
+ */
+SpaceGhost.prototype.logout = function logoutUser(){
+ this.clickLabel( this.labels.masthead.menus.user );
+ this.clickLabel( this.labels.masthead.userMenu.login );
+ this.thenOpen( this.baseUrl, function(){
+ //TODO: handle already logged out
+ this.clickLabel( this.labels.masthead.menus.user );
+ this.clickLabel( this.labels.masthead.userMenu.logout );
+ });
+};
+
+/** Tests logging in a user on the Galaxy instance by submitting the login form.
+ * NOTE: this version does NOT throw an error on a bad login.
+ * It is meant for testing the login functionality and, therefore, is marked as private.
+ * Other tests should use login
+ * @param {String} email the users email address
+ * @param {String} password the users password
+ */
+SpaceGhost.prototype._submitLogin = function logoutUser( email, password ){
+ var loginInfo = {
+ //NOTE: keys are used as name selectors in the fill fn - must match the names of the inputs
+ email: email,
+ password: password
+ };
+
+ this.thenOpen( this.baseUrl, function(){
+
+ this.clickLabel( this.labels.masthead.menus.user );
+ this.clickLabel( this.labels.masthead.userMenu.login );
+
+ this.withFrame( this.selectors.frames.main, function mainBeforeLogin(){
+ this.debug( '(' + this.getCurrentUrl() + ') logging in user:\n' + this.jsonStr( loginInfo ) );
+ this.fill( this.selectors.loginPage.form, loginInfo, false );
+ this.click( xpath( this.selectors.loginPage.submit_xpath ) );
+ });
+ this.withFrame( this.selectors.frames.main, function mainAfterLogin(){
+ //TODO: prob. could use a more generalized form of this for url breakdown/checking
+ if( this.getCurrentUrl().search( this.selectors.loginPage.url_regex ) != -1 ){
+ var messageInfo = this.getElementInfo( this.selectors.messages.all );
+ this.debug( 'post login message:\n' + this.jsonStr( messageInfo ) );
+ }
+ });
+ });
+};
+
+/** Logs in a user. Throws error on bad log in.
+ * @param {String} email the users email address
+ * @param {String} password the users password
+ */
+SpaceGhost.prototype.login = function login( email, password ){
+ this._submitLogin( email, password );
+ this.then( function(){
+ this.withFrame( this.selectors.frames.main, function mainAfterLogin(){
+ if( this.getCurrentUrl().search( this.selectors.loginPage.url_regex ) != -1 ){
+ var messageInfo = this.getElementInfo( this.selectors.messages.all );
+ if( messageInfo && messageInfo.attributes[ 'class' ] === 'errormessage' ){
+ throw new GalaxyError( 'LoginError: ' + messageInfo.html );
+ }
+ }
+ });
+ if( this.loggedInAs() === email ){
+ this.debug( 'logged in as ' + email );
+ }
+ });
+ return this;
+};
+
+/** Fetch the email of the currently logged in user (or '' if not logged in)
+ * @returns {String} email of currently logged in user or '' if no one logged in
+ */
+SpaceGhost.prototype.loggedInAs = function loggedInAs(){
+ var userEmail = '';
+ try {
+ var loggedInInfo = this.getElementInfo( xpath( this.selectors.masthead.userMenu.userEmail_xpath ) );
+ userEmail = loggedInInfo.text;
+ } catch( err ){
+ this.error( err );
+ }
+ //console.debug( 'loggedInInfo:', this.jsonStr( loggedInInfo ) );
+ return userEmail;
+};
+
+/** Attempts to login a user - if that raises an error (LoginError), register the user
+ * @param {String} email the users email address
+ * @param {String} password the users password
+ * @param {String} username the users ...username! (optional: will use 1st part of email)
+ */
+SpaceGhost.prototype.loginOrRegisterUser = function loginOrRegisterUser( email, password, username ){
+ // attempt a login, if that fails - register
+ this.tryStepsCatch( function tryToLogin(){
+ this.open( this.baseUrl ).login( email, password );
+
+ }, function failedLoginRegister(){
+ this.open( this.baseUrl ).registerUser( email, password, username );
+ });
+ return this;
+};
+
+/** Tests uploading a file.
+ * NOTE: this version does NOT throw an error on a bad upload.
+ * It is meant for testing the upload functionality and, therefore, is marked as private.
+ * Other tests should use uploadFile
+ * @param {String} filepath the local filesystem path of the file to upload (absolute (?))
+ */
+SpaceGhost.prototype._uploadFile = function _uploadFile( filepath ){
+ var uploadInfo = {};
+ //TODO: check file exists using phantom.fs
+ //TODO: pull from test data
+ uploadInfo[ this.tools.upload.fileInput ] = filepath;
+ this.debug( 'uploading file: ' + filepath );
+
+ spaceghost.then( function(){
+ spaceghost.withFrame( this.selectors.frames.tools, function(){
+ this.clickLabel( this.tools.upload.panelLabel );
+ });
+ });
+
+ this.then( function beginUpload(){
+ spaceghost.withFrame( this.selectors.frames.main, function(){
+ this.fill( this.tools.general.form, uploadInfo, false );
+
+ // the following throws:
+ // [error] [remote] Failed dispatching clickmouse event on xpath selector: //input[@value="Execute"]:
+ // PageError: TypeError: 'undefined' is not a function (evaluating '$(this).formSerialize()')
+
+ // ...and yet the upload still seems to work
+ this.click( xpath( this.tools.general.executeButton_xpath ) );
+ });
+ });
+ this.withFrame( this.selectors.frames.main, function afterUpload(){
+ var messageInfo = this.elementInfoOrNull( this.selectors.messages.all );
+ this.debug( 'post upload message:\n' + this.jsonStr( messageInfo ) );
+ });
+};
+
+/** Uploads a file.
+ * @param {String} filepath the local filesystem path of the file to upload (absolute (?))
+ */
+SpaceGhost.prototype.uploadFile = function uploadFile( filepath ){
+ this._uploadFile( filepath );
+ this.then( function(){
+ this.withFrame( this.selectors.frames.main, function mainAfterUpload(){
+ var messageInfo = this.elementInfoOrNull( this.selectors.messages.all );
+ if( ( !messageInfo )
+ || ( messageInfo.attributes[ 'class' ] !== 'donemessagelarge' )
+ || ( messageInfo.text.indexOf( this.text.upload.success ) === -1 ) ){
+ throw new GalaxyError( 'UploadError: ' + this.jsonStr( messageInfo ) );
+ }
+ });
+ });
+ return this;
+};
+
+/** Parses the hid and name of a newly uploaded file from the tool execution donemessagelarge
+ * @param {String} doneMsgText the text extracted from the donemessagelarge after a tool execution
+ */
+SpaceGhost.prototype.parseDoneMessageForTool = function parseDoneMessageForTool( doneMsgText ){
+ //TODO: test on non-upload
+ var executionInfo = {};
+ var textMatch = doneMsgText.match( /added to the queue:\n\n(\d+)\: (.*)\n/m );
+ if( textMatch ){
+ if( textMatch.length > 1 ){
+ executionInfo.hid = parseInt( textMatch[1], 10 );
+ }
+ if( textMatch.length > 2 ){
+ executionInfo.name = textMatch[2];
+ }
+ executionInfo.name = textMatch[2];
+ }
+ return executionInfo;
+};
+
+/** Find the casper element info of the hda wrapper given the hda title and hid.
+ * NOTE: if more than one is found, will return the first found.
+ * precondition: you should wrap this with withFrame( 'galaxy_history' ) :(
+ * @param {String} title the title of the hda
+ * @param {Int} hid (optional) the hid of the hda to look for
+ * @returns {Object|null} ElementInfo of the historyItemWrapper found, null if not found
+ */
+SpaceGhost.prototype.hdaElementInfoByTitle = function hdaElementInfoByTitle( title, hid ){
+ var spaceghost = this,
+ titleContains = ( hid !== undefined )?( hid + ': ' + title ):( title ),
+ wrapperInfo = null;
+
+ wrapperInfo = spaceghost.evaluate( function( titleContains ){
+ // find the title, then the wrapper (2 containers up)
+ var $title = $( '.historyItemTitle:contains(' + titleContains + ')' );
+ var $wrapper = $title.parent().parent();
+ return (( $wrapper.attr( 'id' ) )?( __utils__.getElementInfo( '#' + $wrapper.attr( 'id' ) )):( null ));
+ }, titleContains );
+
+ return wrapperInfo;
+};
+
+/** Wait for the hda with given id to move into the given state.
+ * @param {String} hdaSelector selector for hda (should be historyItemWrapper)
+ * @param {String} finalState hda state to wait for (e.g. 'ok', 'error', 'running', 'queued', etc.)
+ * @param {Function} whenInStateFn called when hda goes into finalState
+ * @param {Function} timeoutFn called when maxWaitMs have passed without the desired state
+ * @param {Int} maxWaitMs number of milliseconds to wait before timing out (defaults to options.waitTimeout)
+ */
+SpaceGhost.prototype.waitForHdaState = function waitForHdaState( hdaSelector, finalState,
+ whenInStateFn, timeoutFn, maxWaitMs ){
+ //TODO:?? explicitly a historyWrapper id?
+ maxWaitMs = maxWaitMs || this.options.waitTimeout;
+ var finalStateClass = '.historyItem-' + finalState;
+
+ this.then( function(){
+ this.withFrame( this.selectors.frames.history, function(){
+ // wait for state, preferrably debugging intermediate states
+ var spaceghost = this,
+
+ // we need a larger timeout for these - it can take a bit
+ oldWaitTimeout = this.options.waitTimeout,
+
+ // output some progress indicator within the test (debug)
+ progressIntervalId = setInterval( function progress(){
+ var state = spaceghost.evaluate( function( hdaSelector ){
+ var $wrapperClasses = $( hdaSelector ).attr( 'class' );
+ return $wrapperClasses.match( /historyItem\-(\w+)/ )[1];
+ }, hdaSelector );
+ spaceghost.debug( hdaSelector + ': ' + state );
+ }, 1000 ),
+
+ // when done, close down the progress reporter and reset the wait timeout to what it was
+ finallyFn = function(){
+ spaceghost.options.waitTimeout = oldWaitTimeout;
+ clearInterval( progressIntervalId );
+ };
+
+ this.options.waitTimeout = maxWaitMs;
+ this.waitForSelector( hdaSelector + finalStateClass, function _whenInState(){
+ this.debug( 'HDA now in state ' + finalState + ':\n'
+ + this.jsonStr( this.elementInfoOrNull( hdaSelector ) ) );
+ whenInStateFn.call( this );
+ finallyFn();
+
+ }, function timeout(){
+ this.debug( 'timed out:\n'
+ + this.jsonStr( this.elementInfoOrNull( hdaSelector ) ) );
+ timeoutFn.call( this );
+ finallyFn();
+ }
+ );
+ });
+ });
+};
+
+// =================================================================== MISCELAIN
+/** Send message to stderr
+ */
+SpaceGhost.prototype.stderr = function( msg ){
+ var fs = require( 'fs' );
+ fs.write( '/dev/stderr', msg + '\n', 'w' );
+};
+
+// convenience logging funcs
+/** log using level = 'debug' and default namespace = 'spaceghost'
+ */
+SpaceGhost.prototype.debug = function( msg, namespace ){
+ namespace = namespace || 'spaceghost';
+ this.log( msg, 'debug', namespace );
+};
+
+/** log using level = 'info' and default namespace = 'spaceghost'
+ */
+SpaceGhost.prototype.info = function( msg, namespace ){
+ namespace = namespace || 'spaceghost';
+ this.log( msg, 'info', namespace );
+};
+
+/** log using level = 'info' and default namespace = 'spaceghost'
+ */
+SpaceGhost.prototype.warning = function( msg, namespace ){
+ namespace = namespace || 'spaceghost';
+ this.log( msg, 'warning', namespace );
+};
+
+/** log using level = 'info' and default namespace = 'spaceghost'
+ */
+SpaceGhost.prototype.error = function( msg, namespace ){
+ namespace = namespace || 'spaceghost';
+ this.log( msg, 'error', namespace );
+};
+
+/** log despite logLevel settings, unless returnJsonOnly is set
+ */
+SpaceGhost.prototype.out = function( msg, namespace ){
+ if( !this.options.returnJsonOnly ){
+ console.debug( msg );
+ }
+};
+
+/** JSON formatter
+ */
+SpaceGhost.prototype.jsonStr = function( obj ){
+ return JSON.stringify( obj, null, 2 );
+};
+
+/** Debug SG itself
+ */
+SpaceGhost.prototype.debugMe = function(){
+ console.debug( 'options:\n' + this.jsonStr( this.options ) );
+ console.debug( 'cli:\n' + this.jsonStr( this.cli ) );
+};
+
+/** Get the last error on the stack.
+ */
+SpaceGhost.prototype.lastError = function(){
+ return this.errors[( this.errors.length - 1 )];
+};
+
+/** Get the last error from an assertRaises test (gen. for the message)
+ */
+SpaceGhost.prototype.getLastAssertRaisesError = function(){
+ // assuming the test passed here...
+ var testsThatPassed = this.test.testResults.passes;
+ var test = null;
+ for( var i=( testsThatPassed.length - 1 ); i>=0; i-- ){
+ currTest = testsThatPassed[i];
+ if( currTest.type === 'assertRaises' ){
+ test = currTest; break;
+ }
+ }
+ return ( ( test && test.values )?( test.values.error ):( undefined ) );
+};
+
+/** String representation
+ */
+SpaceGhost.prototype.toString = function(){
+ var currentUrl = '';
+ try {
+ currentUrl = this.getCurrentUrl();
+ } catch( err ){}
+ return 'SpaceGhost(' + currentUrl + ')';
+};
+
+
+// =================================================================== TEST DATA
+// maintain selectors, labels, text here in one central location
+
+//TODO: to separate file?
+SpaceGhost.prototype.selectors = {
+ masthead : {
+ userMenu : {
+ userEmail : 'a #user-email',
+ userEmail_xpath : '//a[contains(text(),"Logged in as")]/span["id=#user-email"]'
+ }
+ },
+ frames : {
+ main : 'galaxy_main',
+ tools : 'galaxy_tools',
+ history : 'galaxy_history'
+ },
+ messages : {
+ all : '[class*="message"]',
+ error : '.errormessage',
+ done : '.donemessage'
+ },
+ loginPage : {
+ form : 'form#login',
+ submit_xpath : "//input[@value='Login']",
+ url_regex : /\/user\/login/
+ },
+ registrationPage : {
+ form : 'form#registration',
+ submit_xpath : "//input[@value='Submit']"
+ }
+};
+
+SpaceGhost.prototype.labels = {
+ masthead : {
+ menus : {
+ user : 'User'
+ },
+ userMenu : {
+ register : 'Register',
+ login : 'Login',
+ logout : 'Logout'
+ }
+ }
+};
+
+SpaceGhost.prototype.tools = {
+ general : {
+ form : 'form#tool_form',
+ executeButton_xpath : '//input[@value="Execute"]'
+ },
+ upload : {
+ panelLabel : 'Upload File',
+ fileInput : 'files_0|file_data' // is this general?
+ }
+};
+
+SpaceGhost.prototype.text = {
+ registrationPage : {
+ badEmailError : 'Enter a real email address'
+ //...
+ },
+ upload : {
+ success : 'The following job has been successfully added to the queue'
+ }
+};
+
+// =================================================================== EXPORTS
+/**
+ */
+exports.SpaceGhost = SpaceGhost;
+exports.PageError = PageError;
+exports.GalaxyError = GalaxyError;
+exports.AlertError = AlertError;
+/**
+ */
+exports.create = function create(options) {
+ "use strict";
+ return new SpaceGhost(options);
+};
+
+// ------------------------------------------------------------------- included libs
+//??: can we require underscore, etc. from the ../../static/scripts/lib?
+// yep!
+//var _ = require( '../../static/scripts/libs/underscore' );
+//var stooges = [{name : 'moe', age : 40}, {name : 'larry', age : 50}, {name : 'curly', age : 60}];
+//console.debug( JSON.stringify( _.pluck(stooges, 'name') ) );
+//exports._ = _;
diff -r ba8c49884f7daab5df8b62bd631157058c7ee910 -r 01e73b11a46f87b03af29581603378b06187051d test/casperjs/upload-tests.js
--- /dev/null
+++ b/test/casperjs/upload-tests.js
@@ -0,0 +1,132 @@
+// have to handle errors here - or phantom/casper won't bail but _HANG_
+try {
+ var utils = require( 'utils' ),
+ xpath = require( 'casper' ).selectXPath,
+ format = utils.format,
+
+ //...if there's a better way - please let me know, universe
+ scriptDir = require( 'system' ).args[3]
+ // remove the script filename
+ .replace( /[\w|\.|\-|_]*$/, '' )
+ // if given rel. path, prepend the curr dir
+ .replace( /^(?!\/)/, './' ),
+ spaceghost = require( scriptDir + 'spaceghost' ).create({
+ // script options here (can be overridden by CLI)
+ //verbose: true,
+ //logLevel: debug,
+ scriptDir: scriptDir
+ });
+
+ spaceghost.start();
+
+} catch( error ){
+ console.debug( error );
+ phantom.exit( 1 );
+}
+
+
+// ===================================================================
+/* TODO:
+
+ find a way to error on bad upload?
+ general tool execution
+*/
+// =================================================================== globals and helpers
+var email = spaceghost.getRandomEmail(),
+ password = '123456';
+if( spaceghost.fixtureData.testUser ){
+ email = spaceghost.fixtureData.testUser.email;
+ password = spaceghost.fixtureData.testUser.password;
+}
+
+
+// =================================================================== TESTS
+// ------------------------------------------------------------------- start a new user
+spaceghost.loginOrRegisterUser( email, password );
+//??: why is a reload needed here? If we don't, loggedInAs === '' ...
+spaceghost.thenOpen( spaceghost.baseUrl, function(){
+ var loggedInAs = spaceghost.loggedInAs();
+ this.test.assert( loggedInAs === email, 'loggedInAs() matches email: "' + loggedInAs + '"' );
+});
+
+// ------------------------------------------------------------------- get avail. tools
+// list available tools
+//spaceghost.then( function(){
+// spaceghost.withFrame( 'galaxy_tools', function(){
+// //var availableTools = this.fetchText( 'a.tool-link' );
+//
+// var availableTools = this.evaluate( function(){
+// //var toolTitles = __utils__.findAll( 'div.toolTitle' );
+// //return Array.prototype.map.call( toolTitles, function( e ){
+// // //return e.innerHtml;
+// // return e.textContent || e.innerText;
+// //}).join( '\n' );
+//
+// var toolLinks = __utils__.findAll( 'a.tool-link' );
+// return Array.prototype.map.call( toolLinks, function( e ){
+// //return e.innerHtml;
+// return e.textContent || e.innerText;
+// }).join( '\n' );
+// });
+// this.debug( 'availableTools: ' + availableTools );
+// });
+//});
+
+// ------------------------------------------------------------------- upload from fs
+// test uploading from the filesystem
+var uploadInfo = {};
+spaceghost.then( function(){
+ // strangely, this works with a non-existant file --> empty txt file
+ var filename = '1.sam';
+ var filepath = this.options.scriptDir + '/../../test-data/' + filename;
+ this._uploadFile( filepath );
+
+ // when an upload begins successfully...
+ // 1. main should reload with a donemessagelarge
+ // 2. which contains the uploaded file's new hid
+ // 3. and the filename of the upload
+ this.withFrame( 'galaxy_main', function(){
+ var doneElementInfo = this.elementInfoOrNull( '.donemessagelarge' );
+ this.test.assert( doneElementInfo !== null,
+ "Found donemessagelarge after uploading file" );
+
+ uploadInfo = this.parseDoneMessageForTool( doneElementInfo.text );
+ this.test.assert( uploadInfo.hid >= 0,
+ 'Found sensible hid from upload donemessagelarge: ' + uploadInfo.hid );
+ this.test.assert( uploadInfo.name === filename,
+ 'Found matching name from upload donemessagelarge: ' + uploadInfo.name );
+ });
+
+});
+
+// wait for upload to finish
+spaceghost.then( function(){
+ var hdaInfo = null;
+
+ this.withFrame( 'galaxy_history', function(){
+ hdaInfo = this.hdaElementInfoByTitle( uploadInfo.name, uploadInfo.hid );
+ this.debug( 'hda:\n' + this.jsonStr( hdaInfo ) );
+ });
+
+ this.then( function(){
+ this.test.comment( 'Waiting for upload to move to ok state in history' );
+ //precondition: needs class
+ var hdaStateClass = hdaInfo.attributes[ 'class' ].match( /historyItem\-(\w+)/ )[0];
+ if( hdaStateClass !== 'historyItem-ok' ){
+ this.waitForHdaState( '#' + hdaInfo.attributes.id, 'ok',
+ function whenInStateFn(){
+ this.test.assert( true, 'Upload completed successfully for: ' + uploadInfo.name );
+
+ }, function timeoutFn(){
+ this.test.fail( 'Test timedout for upload: ' + uploadInfo.name );
+
+ // wait a maximum of 30 secs
+ }, 30 * 1000 );
+ }
+ });
+});
+
+// ===================================================================
+spaceghost.run( function(){
+ this.test.done();
+});
diff -r ba8c49884f7daab5df8b62bd631157058c7ee910 -r 01e73b11a46f87b03af29581603378b06187051d test/casperjs/utils/simple-galaxy.js
--- /dev/null
+++ b/test/casperjs/utils/simple-galaxy.js
@@ -0,0 +1,1 @@
+/Users/carleberhard/explore/phantom-casper/simple-galaxy.js
\ No newline at end of file
Repository URL: https://bitbucket.org/galaxy/galaxy-central/
--
This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.
1
0