Wednesday, 7 October 2009
Testing grails scripts
http://www.cacoethes.co.uk/blog/groovyandgrails/testing-your-grails-scripts
Thursday, 17 September 2009
Build Server Woes (mptscsi task abort)
- in the hope that someone out there will find this useful
- personal therapy
We've got a couple of build servers (x86_64 linux, openVZ) that have been having some disk I/O problems. These boxes (boxen) run various virtual machines related to our product builds - hudson masters & slaves, distribution servers, puppet master, test mail server, munin server... yada yada. You get the idea. They're kinda important.
The problem manifests itself by first reporting errors like the following:
Sep 16 14:28:51 hn3 mptscsih: ioc0: attempting task abort! (sc=ffff880422a348c0)
Sep 16 14:28:51 hn3 sd 0:1:2:0: [sda] CDB: cdb[0]=0x2a: 2a 00 12 b2 fc 9f 00 00 08 00
Sep 16 14:28:51 hn3 mptscsih: ioc0: Issue of TaskMgmt failed!
followed shortly by the volume in question getting offlined into readonly mode:
Sep 16 14:29:41 hn3 mptscsih: ioc0: host reset: SUCCESS (sc=ffff880422a348c0)
Sep 16 14:29:41 hn3 sd 0:1:2:0: Device offlined - not ready after error recovery
This ain't that helpful when you've got a whole load of hungry VMs wanting to write stuff to disk. A closer look at the disk controller yields the following:
>lspci
...
0b:00.0 SCSI storage controller: LSI Logic / Symbios Logic SAS1068E PCI-Express Fusion-MPT SAS (rev 08)
...
A quick search through our messages shows the following related information:
>dmesg | grep -i mpt
Fusion MPT base driver 3.04.07
Fusion MPT SPI Host driver 3.04.07
Fusion MPT FC Host driver 3.04.07
Fusion MPT SAS Host driver 3.04.07
mptsas 0000:0b:00.0: PCI INT A -> GSI 35 (level, low) -> IRQ 35
mptbase: ioc0: Initiating bringup
mptbase: ioc0: PCI-MSI enabled
mptsas 0000:0b:00.0: setting latency timer to 64
Fusion MPT misc device (ioctl) driver 3.04.07
mptctl: Registered with Fusion MPT base driver
mptctl: /dev/mptctl @ (major,minor=10,220)
A quick google turns up quite a few different issues with these controllers but no clear resolution (no surprise).
To cut a (very) long story short we appear to have a solution by using the drivers supplied by LSI rather than those shipped with the latest linux kernel. Patching the kernel (by replacing the drivers/message/fusion folder with the equivalent found in LSI's MPTLINUX_RHEL5_SLES10_PH16-4.18.00.00-1.zip distribution) with version 4.18 of the MPT drivers has yielded an (apparently) stable system, tested under reasonably high load (load average ~20).
Incidentally, for those of you who like acronyms, MPT stands for 'Message passing technology'.
My work here is done.
Monday, 17 August 2009
Dynamic UrlMapping using request parameters
"/update/$controller/$target" {
action = {
"update" + params.target[0].toUpperCase() + params.target.substring(1);
}
}
Great for mapping restful style urls to meaningful action names, e.g.
"/update/patient/speciality" => PatientController.updateSpeciality
Sunday, 5 July 2009
Selenium IDE 1.0.1
storeText //h2 varname
It turned out that if the text is hidden on the page then all that is stored is ''. In our case it was fixture pages that was returning some data as hidden page elements for reference.
Wednesday, 24 June 2009
Selenium IDE
(on linux)
wmctrl -r 'Mozilla Firefox' -e 0,0,25,1330,1125
wmctrl -r "Mozilla Firefox" -b remove,maximized_vert,maximized_horz
wmctrl -a 'Mozilla Firefox'
wmctrl -r 'Selenium IDE' -e 0,1350,25,575,1125
wmctrl -a 'Selenium IDE'
The snippet above moves, unmaximises and raises firefox before moving and activiating the Selenium IDE.
Tuesday, 23 June 2009
Custom constraint
One thing that we did notice was that they suggest that you put the registering of the custom validator in the Config.groovy file. We found this problematic and found that the better solution was to put it in the resources.groovy file. Another problem that we came across was that you needed to register the constraint in the unit test if you wanted to be able to test constraints. This makes sense because resources.groovy isn't called in unit tests.
To register your constraint use the following line of groovy code:
ConstrainedProperty.registerNewConstraint(PhoneNumberConstraint.NAME, PhoneNumberConstraint)
Hopefully this helps with some refactoring.Glenn
Wednesday, 29 April 2009
Selenium CSS locators
It turns out this is a common problem and is due to the lack for native xpath support in IE. So for all the selenium tests that use xpath=//..... in IE the xpath is actually being evaluated using javascript ... ouch. An alternative is to use css locators, you can't do this everywhere but as well as improving the IE performance it can also provide some rather tidy rules. For example
xpath=//div[contains(@class,'balance')]
or
css=.balance
xpath=//div[@id,'topLeft')//span[contains(@class,'name')]
or
css=#topLeft .name
So even if you dont' care about IE using css selectors might be a lot nicer. (It might also improve your css skills!)
Thursday, 23 April 2009
GMock Talk
Please register if you are interested:
http://skillsmatter.com/event/java-jee/gmock-be-groovy-when-writing-tests
Wednesday, 15 April 2009
instanceof vs. HibernateProxy and inheritance
Sunday, 12 April 2009
IE hangs with "Waiting for 1 resource to load"
http://groups.google.com/group/prototype-scriptaculous/browse_thread/thread/a1c745463251a95d?pli=1
The suggested solution of replacing src=:// with src=blank.js worked for us.
Friday, 3 April 2009
Timing Selenium
Add the following test to the start of your suite
open|/|
storeEval|{lastTime = new Date().getTime(); 'x'}|x
storeEval|{LOG.info = function(message) { var now = new Date().getTime(); var duration = now - lastTime; this.warn(message + duration); lastTime = now}; 'x';}|x
When your run your suite the log file file contain lines like...
warn: Starting test /selenium-server/tests/suite-tests/conversation/SearchConversationTest.html119
warn: Executing: |open | /fixture/tearDownTestData | |47
warn: Executing: |assertTitle | Fixture | |239
warn: Executing: |invokeUrl | /fixture/reindexSearch | |28
warn: Executing: |invokeUrl | /fixture/setUpPersons | |121
warn: Executing: |invokeUrl | /fixture/setUpConversationsForSearch | |212
warn: Executing: |createCookie | splash=false | path=/ |16638
The duration for each step has been appended to the NEXT log line, i.e. setUpConversationsForSearch took 16638ms
The next step is to parse this file into something readable. We wrote a groovy script (see below), which generates output like:
===SELENIUM TEST TIMINGS===
69763=HomepageTest
54106=SubscribeToTribeConversationTest
50902=SubscribeToTagTest
48734=TagDedupingTest
44614=SubscribeToPrivateConversationTest
===INDIVIDUAL COMMAND TIMINGS===
153127=loginAndWait hits=291 avg=526.2096219931
103428=open /person/ hits=241 avg=429.1618257261
90431=open /logout hits=224 avg=403.7098214286
70059=invokeUrl /fixture/setUpConversationsForSearch hits=4 avg=17514.75
61926=open /login hits=246 avg=251.7317073171
61851=open / hits=133 avg=465.045112782
61034=createMessageAndWait hits=114 avg=535.3859649123
So now it's trivial to identify slow running tests / test steps when optimising our build. It's also useful to spot slow loading pages.
Here's the groovy script, it's tailored to our environment but a good starting point.
import org.codehaus.groovy.tools.LoaderConfiguration
import org.codehaus.groovy.tools.RootLoader
import org.codehaus.groovy.grails.commons.cfg.ConfigurationHelper
import org.codehaus.groovy.grails.commons.ConfigurationHolder
import groovy.text.GStringTemplateEngine
import org.apache.commons.lang.StringUtils
Ant.property(environment:'env')
grailsHome = Ant.antProject.properties.'env.GRAILS_HOME'
grailsEnv = System.properties.'grails.env';
pluginDir = this.binding['seleniumPluginDir']
includeTargets <<> timings = []
int nextId = 0
String currentTest = 'anonymous-test'
SeleniumTiming previousTiming
SeleniumTiming currentTiming
class SeleniumTiming {
String id
String test
String command
String target
String value
int duration = 0
String getTargetKey() {
def trimmedTarget = target?.trim()
def trimmedCommand = command?.trim()
if (trimmedCommand == 'open' && trimmedTarget ==~ '/person/\\w+') {
return '/person/'
} else if (trimmedCommand == 'open' && trimmedTarget ==~ '/tag/[\\w-]+') {
return '/tag/'
} else if (trimmedCommand == 'open' && trimmedTarget ==~ '/station/[\\w-]+') {
return '/station/'
} else if (trimmedCommand == 'open' && trimmedTarget ==~ '/line/[\\w-]+') {
return '/line/'
} else if (trimmedCommand == 'open' && trimmedTarget ==~ '/conversation/subscribe?.*') {
return '/conversation/subscribe'
} else if (trimmedCommand == 'open' && trimmedTarget ==~ '/conversation/unsubscribe?.*') {
return '/conversation/unsubscribe'
} else if (trimmedCommand == 'loginAndWait') {
return ''
} else if (trimmedCommand == 'sendMessageAndWait') {
return ''
} else if (trimmedCommand == 'createMessageAndWait') {
return ''
} else if (trimmedCommand == 'reply') {
return ''
} else if (trimmedCommand == 'replyAndWait') {
return ''
} else if (trimmedCommand == 'registerAndWait') {
return ''
} else if (trimmedCommand == 'reportThisAndWait') {
return ''
} else {
return target
}
}
String toString() {
"[$test] $command($target,$value):$duration"
}
}
def parseTestLine = { String input ->
currentTest = input.substring(input.lastIndexOf(File.separator)+1, input.lastIndexOf('.html'))
}
def parseCommandLine = { String input ->
List data = input.tokenize('|')
if(data.size() == 5) {
previousTiming = currentTiming
currentTiming = new SeleniumTiming(id:nextId++, test: currentTest, command:data[1],
target:data[2], value:data[3])
timings << duration =" Integer.parseInt(data[4])" parsefile =" {">
input.eachLine { String line ->
if(line.startsWith('warn: Starting test')) {
parseTestLine line
} else if(line.startsWith('warn: Executing: |')) {
parseCommandLine line
}
}
}
target ('default': 'Generate Selenium Timings Report') {
loadSeleniumConfig seleniumConfig
new File(seleniumConfig.reportdir).eachFileMatch(~/.*\.html/, parseFile)
def longestFirst = [ compare: { a, b -> a.equals(b) ? 0: Math.abs(a) < overalltimings =" new">
overallTimings[(values*.duration).sum()] = key
}
Map commandTimings = new TreeMap(longestFirst)
timings.groupBy({ "${it.command} ${it.targetKey}" }).each { key, values ->
int totalDuration = (values*.duration).sum()
int noOfValues = values.size()
commandTimings[totalDuration] = "$key hits=${noOfValues} avg=${totalDuration / noOfValues}"
}
new File(seleniumConfig.reportdir, 'timings.txt').withPrintWriter { writer ->
writer.println '===SELENIUM TEST TIMINGS==='
overallTimings.each { writer.println it }
writer.println '===INDIVIDUAL COMMAND TIMINGS==='
commandTimings.each { writer.println it }
}
commandTimings.each { println it }
}
Saturday, 21 March 2009
Release of gmock 0.7.0
Strict ordering is accomplished through the ordered closure. Here is an example with an hypothetic cached cat database:
def database = mock()
def cache = mock()
ordered {
database.open()
cache.get("select * from cat").returns(null)
database.query("select * from cat").returns(["cat1", "cat2"])
cache.put("select * from cat", ["cat1", "cat2"])
database.close()
}
The partial mocking is performed simply by using the mock method on your concrete object. Here is how it works with a grails controller:
def controller = new SomeController()
mock(controller).params.returns = [id: 3]
GMock 0.7.0 is the last release compatible with Groovy 1.5.x. Support for Groovy 1.6.0 is coming soon.
Tuesday, 10 March 2009
Grails 1.1 released
Tuesday, 3 March 2009
Gsp reloading for other environments
This came to light because we do not use production data in our development environment but sometimes it's useful to have production data to test with or to work out a bug. We have a separate environment config for the times we want to run with prod data so this little flag has become a life-saver.
Monday, 2 March 2009
New in Groovy 1.6
whats new in groovy 1.6
Groovy 1.6 will be released with grails 1.1
Sunday, 1 March 2009
Running Selenium Tests In Grails 1.1
Anyone experimenting with Grails 1.1 might be interested to know that I've got the Selenium plugin working. If you put the following entries in your project's grails-app/conf/BuildConfig.groovy
:
grails.plugin.repos.discovery.energizedwork="https://svn.energizedwork.com/skunkworks/grails/plugins"
grails.plugin.repos.distribution.energizedwork="https://svn.energizedwork.com/skunkworks/grails/plugins"
Then type grails install-plugin ew-selenium
you should be good to go.
The command to run tests is grails run-selenium
. The script now runs in the test environment by default so you no longer need to use grails test run-selenium
.
You may need to configure the browser Selenium uses. To do so edit test/selenium/selenese/conf/SeleniumConfig.groovy
and set the selenium.browser
property. For some reason Firefox 3 needs to be specified as *chrome. For example on a Mac I have to use the setting
selenium.browser = "*chrome /Applications/Firefox.app/Contents/MacOS/firefox-bin"
I have no idea why this is necessary. I have upgraded the Selenium Server version included in the plugin but I found this was also necessary with the old 0.9.2 version.
I changed the plugin name to ew-selenium as there is a plugin called selenium on the main Grails plugin repository and it looks like Grails will ignore conflicting names on other repositories.
Thursday, 19 February 2009
Hitlers nightly build fails
Wednesday, 11 February 2009
Case-insensitive ordering using HSQLDB
Postgres was ignoring the case of items, whereas HSQLDB was not - so if I were to order the strings "Al" and "AM", HSQLDB would show "AM" before "Al", but Postgres would show "Al" before "AM".
Given that the behaviour of Postgres (case-insensitive ordering) was the desired behaviour, we wanted to get HSQLDB to do the same. After a lot of searching any many dead ends, we found that you simply use ".ignoreCase()" after an order criteria:
List books = Book.withCriteria() {
ilike('authorName', "%${searchKey}%")
order('authorName', 'asc').ignoreCase()
maxResults(max)
}
Immutable Objects in Groovy
Anyone familiar with Joshua Bloch's book Effective Java will know about the importance of immutable objects. However, writing classes like that in Groovy always seemed to me to be problematic. You can declare the properties final but if the property type is itself mutable encapsulation is broken. Defining properties as private wasn't enough as Groovy lets you access them from anywhere regardless (I'm still not entirely clear why the language even supports it). A combination of final properties with overridden get methods where property types are mutable works but then you're writing almost as much code as you'd need to in Java and who knows what someone can get away with by using the .@ operator!
Google provided the solution this morning. It turns out there is an @Immutable annotation in Groovy that solves the problem in a very neat way even giving you an implementation of equals, hashCode and toString for free. Those methods tend to be pretty much templated in immutable classes anyway so getting effective implementations without writing any code makes a lot of sense and feels very 'Groovy'.
Tuesday, 10 February 2009
Binding to collection fields on command objects
One of the cool new features of Grails 1.1 is the ability to bind form elements to collection properties of domain objects. Unfortunately it doesn't quite work out of the box for command objects.
Suppose I have a command class like this:
class MyCommand {
Map things = [:]
}
and my form posts params like this:
things[key1] = value1
things[key2] = value2
things[key3] = value3
I'd hope that the binder could cope with this and populate my command object's things property with the appropriate keys and values. It doesn't work as it seems that Grails attempts to figure out what the type of the object contained in the collection is. For domain objects this is possible since the hasMany
closure can be used. Command objects don't have this available and so you end up with a nasty stack trace boiling down to a NullPointerException
.
However there is a way to 'fool' the data binder. All it's trying to do is figure out the value type for the Map so it can use an appropriate PropertyEditor to decode the HTTP request parameter value. In this example the value is just a String. Initializing the Map like this provides a workaround:
import org.apache.commons.collections.MapUtils
import org.apache.commons.collections.FactoryUtils
class MyCommand {
Map things = MapUtils.lazyMap([:], FactoryUtils.constantFactory(''))
}
That's using a LazyMap
from Commons Collections which (in case you can't guess) is a Map implementation that populates a default value when get is called for an unmapped key. The Map in the command object will use the empty string as its default value which is enough for the Grails data binder to figure out the value type and bind our parameters.
I haven't tried the same trick with richer data types yet (e.g. a collection of domain objects in a command) but I don't think there's any reason why a similar workaround returning a different value from the Factory shouldn't work.
Sunday, 1 February 2009
Testing Url Mappings
def grailsUrlMappingsHolderBean
void testMappingForPersonController() {
def mappingInfo = grailsUrlMappingsHolderBean.match('/person/pain')
assertEquals 'person', mappingInfo.controllerName
assertEquals 'show', mappingInfo.actionName
assertEquals 'pain', mappingInfo.parameters.id
}
The grails testing plugin provides custom assertions for doing this and more (assertUrlMapping etc.).
kthxbye
Thursday, 22 January 2009
On selenium testing...
Tuesday, 6 January 2009
Groovy Truth and the instanceof Operator
if (!obj instanceof SomeEnum) throw new IllegalArgumentException()Clearly, the intention was to throw
// do something with obj.name()
IllegalArgumentException
if obj
is not the correct type, but instead the code was throwing MissingMethodException
from the access to obj.name()
.The problem is missing braces. What Groovy is actually evaluating is
if (false instanceof SomeEnum)
!obj
evaluates to false as obj
is not null. The correct code would be:if (!(obj instanceof SomeEnum)) throw new IllegalArgumentException()