Gosu support in Cucumber-JVM

515 views
Skip to first unread message

Aslak Hellesøy

unread,
Mar 5, 2014, 7:13:19 PM3/5/14
to gosu...@googlegroups.com
Hi everyone,

I am the creator of Cucumber and Cucumber-JVM [1]

My experience with Gosu is only a few hours - I started implementing cucumber-gosu [2] today so that people can use Cucumber-JVM and write their step definitions [3] in Gosu. Several things are puzzling me.

In JavaScript/Ruby/Python you can put your step definitions in several scripts. My understanding is that the equivalent of a script in Gosu is a "Gosu Program" - a .gsp file.
Is it possible for a .gsp to run another .gsp in the same JVM? This would be necessary in order to define step defintions in multiple files, something Cucumber users typically do to organise their code.

I couldn't figure out to do this, so in my rough prototype I used a class instead, but that seems rather clunky. I'm also not sure what would be the best way to dynamically load many such stepdef classes without knowing their names. Cucumber-JVM scans the filesystem for scripts and loads them. (Cucumber-JVM's Java module uses reflection instead, but let's not go there).

Another small inconvenience I ran into was the lack of gosu jar files in the maven repository. I found the gosu repo, but it only has tarballs, so I ended up adding the gosu jars to git - not ideal.

I'd love some feedback and pointers about this.

Cheers,
Aslak


Scott McKinney

unread,
Mar 5, 2014, 8:56:46 PM3/5/14
to gosu...@googlegroups.com
Hi Aslak.

Nice to see you providing support for Gosu in Cucumber!  You've caught us in the middle of some exciting changes in Gosu.. actually less in the middle and more toward the end.  In any respect I can help you get by with the current release of Gosu and then provide some guidance concerning where Gosu is headed and how you can improve support for it in Cucumber.

So to answer your questions.  Yes, a Gosu Program (.gsp) is the equivalent of a script in dynamic languages.  A program may define any number of classes, functions, and free-from statements, or just a simple expression.  Programs compile to conventional .class files.

As you've discovered Programs aren't intended to be executed from other programs.  The original intention for them was to provide an entry point for an application or to exist as a simple script.  The idea was that if you needed to nominally execute or chain to other code, it's best to write that code as a class, which is what you've done to work around the problem (for now).

You can alternatively execute another program reflectively e.g., 

MyProg.gsp
  ...
  (com.abc.MyOtherProg.Type as java.lang.Class).getDeclaredMethod( "evaluate", {gw.lang.reflect.gs.IExternalSymbolMap} ).invoke( new com.abc.MyOtherProg(), {null})

Sloppy?  You bet!  We can do better with the help of Gosu Enhancements and a little inside information.  Basically we can cleanly wrap this boilerplate reflective call in a new method, execute(), for all Gosu programs by defining a new enhancement on IProgramInstance like so:

MyProgEnhancement.gsx

package aslak

enhancement MyProgEnhancement : gw.lang.reflect.gs.IProgramInstance {
  function execute() {
    (typeof this as java.lang.Class).getDeclaredMethod( "evaluate", 
      {gw.lang.reflect.gs.IExternalSymbolMap} ).invoke( this, {null} )
  }
}

Now you can simply run MyOtherProg from anywhere in Gosu using the new execute() method e.g.,

 
new MyOtherProg().execute()


But no matter how hackolicious this is, it's still a hack.  Your use-case demonstrates that Programs should indeed be able to chain to one another more naturally and that it should be supported in the language definition directly.  Our next release will provide that functionality e.g.,
  
MyOtherProg.evaluate()


As for Maven.  We do currently include the latest Gosu jars.  Your pom file should have entries similar to this: 

<repositories> <repository> <id>gosu-lang.org-releases</id> <name>Official Gosu website (releases)</name> <url>http://gosu-lang.org/repositories/m2/releases</url> </repository> </repositories> <dependencies> <dependency> <groupId>org.gosu-lang.gosu</groupId> <artifactId>gosu</artifactId> <version>0.10.3</version> <type>pom</type> </dependency> ...

We'd be glad to assist you in your effort to support Gosu in Cucumber JVM.  Just let us know.

Cheers.

Scott

aslak hellesoy

unread,
Mar 6, 2014, 5:20:59 AM3/6/14
to gosu...@googlegroups.com
On Thu, Mar 6, 2014 at 1:56 AM, Scott McKinney <rsmck...@hotmail.com> wrote:
Hi Aslak.

Nice to see you providing support for Gosu in Cucumber!  You've caught us in the middle of some exciting changes in Gosu.. actually less in the middle and more toward the end.  In any respect I can help you get by with the current release of Gosu and then provide some guidance concerning where Gosu is headed and how you can improve support for it in Cucumber.

So to answer your questions.  Yes, a Gosu Program (.gsp) is the equivalent of a script in dynamic languages.  A program may define any number of classes, functions, and free-from statements, or just a simple expression.  Programs compile to conventional .class files.

As you've discovered Programs aren't intended to be executed from other programs.  The original intention for them was to provide an entry point for an application or to exist as a simple script.  The idea was that if you needed to nominally execute or chain to other code, it's best to write that code as a class, which is what you've done to work around the problem (for now).

You can alternatively execute another program reflectively e.g., 

MyProg.gsp
  ...
  (com.abc.MyOtherProg.Type as java.lang.Class).getDeclaredMethod( "evaluate", {gw.lang.reflect.gs.IExternalSymbolMap} ).invoke( new com.abc.MyOtherProg(), {null})


Thanks Scott! That got me a lot further. I ended up generating the source of a Gosu Program and invoke it with Gosu.start().


Now I have a new problem. First, I'm trying to define some global functions (Given, When Then) that the user will call from .gsp scripts to define step definitions. I couldn't figure out how to define a global function though. Is it possible, or can you recommend another Gosu-idiomatic way to do this? It would also be acceptable to have a global object or some easily-imported static methods that could be called.
I need type jar (the default dependency type), not type pom.

I don't see any jars in the repo - only tarballs:

Maven can't extract jars from tarballs.

Am I missing something here?

Cheers,
Aslak
  
We'd be glad to assist you in your effort to support Gosu in Cucumber JVM.  Just let us know.

Cheers.

Scott




On Wednesday, March 5, 2014 4:13:19 PM UTC-8, Aslak Hellesøy wrote:
Hi everyone,

I am the creator of Cucumber and Cucumber-JVM [1]

My experience with Gosu is only a few hours - I started implementing cucumber-gosu [2] today so that people can use Cucumber-JVM and write their step definitions [3] in Gosu. Several things are puzzling me.

In JavaScript/Ruby/Python you can put your step definitions in several scripts. My understanding is that the equivalent of a script in Gosu is a "Gosu Program" - a .gsp file.
Is it possible for a .gsp to run another .gsp in the same JVM? This would be necessary in order to define step defintions in multiple files, something Cucumber users typically do to organise their code.

I couldn't figure out to do this, so in my rough prototype I used a class instead, but that seems rather clunky. I'm also not sure what would be the best way to dynamically load many such stepdef classes without knowing their names. Cucumber-JVM scans the filesystem for scripts and loads them. (Cucumber-JVM's Java module uses reflection instead, but let's not go there).

Another small inconvenience I ran into was the lack of gosu jar files in the maven repository. I found the gosu repo, but it only has tarballs, so I ended up adding the gosu jars to git - not ideal.

I'd love some feedback and pointers about this.

Cheers,
Aslak


--
You received this message because you are subscribed to a topic in the Google Groups "gosu-lang" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/gosu-lang/yMJnzQwuFpo/unsubscribe.
To unsubscribe from this group and all its topics, send an email to gosu-lang+...@googlegroups.com.
To post to this group, send email to gosu...@googlegroups.com.
Visit this group at http://groups.google.com/group/gosu-lang.
For more options, visit https://groups.google.com/groups/opt_out.

Luca Boasso

unread,
Mar 6, 2014, 12:21:53 PM3/6/14
to gosu...@googlegroups.com
Are you referring to these jars?

http://gosu-lang.org/nexus/content/groups/releases/org/gosu-lang/gosu/gosu-core/0.10.3/
http://gosu-lang.org/nexus/content/groups/releases/org/gosu-lang/gosu/gosu-core-api/0.10.3/

They should be pulled by maven given the previously mentioned dependencies.
Try with this:

    <dependency>
      <groupId>org.gosu-lang.gosu</groupId>
      <artifactId>gosu-core-api</artifactId>
      <version>${project.parent.version}</version>
    </dependency>
    <dependency>
      <groupId>org.gosu-lang.gosu</groupId>
      <artifactId>gosu-core</artifactId>
      <version>${project.parent.version}</version>
      <scope>runtime</scope>
    </dependency>
    <dependency>

Sorry,  maybe I don't understand your question.
Let me know if that works.

Cheers,
Luca

Luca Boasso

unread,
Mar 6, 2014, 12:24:15 PM3/6/14
to gosu...@googlegroups.com
Ops, I forgot to replace ${project.parent.version} with 0.10.3



    <dependency>
      <groupId>org.gosu-lang.gosu</groupId>
      <artifactId>gosu-core-api</artifactId>
      <version>0.10.3</version>

    </dependency>
    <dependency>
      <groupId>org.gosu-lang.gosu</groupId>
      <artifactId>gosu-core</artifactId>
      <version>0.10.3</version>

      <scope>runtime</scope>
    </dependency>
    <dependency>


Scott McKinney

unread,
Mar 6, 2014, 12:53:45 PM3/6/14
to gosu...@googlegroups.com
Now I have a new problem. First, I'm trying to define some global functions (Given, When Then) that the user will call from .gsp scripts to define step definitions. I couldn't figure out how to define a global function though. Is it possible, or can you recommend another Gosu-idiomatic way to do this? It would also be acceptable to have a global object or some easily-imported static methods that could be called.

Probably your best bet right now is to defined a class with static methods e.g.,

Stuff.gs

package aslak

class Stuff {
  static function Given() {
    ...
  }

  static function When() {
    ...
  }

  ...
}

The your programs can use Stuff and call static methods e.g.,

MyProg.gsp

uses aslak.Stuff
...
Stuff.When()

Will that suffice? 

We have a better way in the up coming release where your program can extend a class and avoid qualifying the function calls:

MyProg.gsp

uses aslak.Stuff

extends Stuff

When()

Another benefit of this approach is that the methods in Stuff could be changed to instance methods and state can be maintained with instance fields.

Hope this helps.

Scott



On Thursday, March 6, 2014 2:20:59 AM UTC-8, Aslak Hellesøy wrote:

aslak hellesoy

unread,
Mar 6, 2014, 4:55:57 PM3/6/14
to gosu...@googlegroups.com
Thanks Luca. The gosu-core and gosu-core-api artifacts is what I was after - Scott pointed me to the gosu artifact which only has poms and tarballs. Miscommunication resolved.

Aslak

aslak hellesoy

unread,
Mar 6, 2014, 4:58:40 PM3/6/14
to gosu...@googlegroups.com
On Thu, Mar 6, 2014 at 5:53 PM, Scott McKinney <rsmck...@hotmail.com> wrote:
Now I have a new problem. First, I'm trying to define some global functions (Given, When Then) that the user will call from .gsp scripts to define step definitions. I couldn't figure out how to define a global function though. Is it possible, or can you recommend another Gosu-idiomatic way to do this? It would also be acceptable to have a global object or some easily-imported static methods that could be called.

Probably your best bet right now is to defined a class with static methods e.g.,

Stuff.gs

package aslak

class Stuff {
  static function Given() {
    ...
  }

  static function When() {
    ...
  }

  ...
}

The your programs can use Stuff and call static methods e.g.,

MyProg.gsp

uses aslak.Stuff
...
Stuff.When()

Will that suffice? 


It will indeed!
 
We have a better way in the up coming release where your program can extend a class and avoid qualifying the function calls:

MyProg.gsp

uses aslak.Stuff

extends Stuff

When()


That will be perfect!
 
Another benefit of this approach is that the methods in Stuff could be changed to instance methods and state can be maintained with instance fields.


Even better, although static is fine too - there would only be one instance of the cucumber runtime anyway.
 
Hope this helps.


You've been great help - thanks a lot!

Aslak

samarak

unread,
Dec 12, 2016, 3:34:29 PM12/12/16
to gosu-lang

Hi everyone,


Gosu support in Cucumber (cucumber-gosu module) is currently broken. The module has been temporarily removed from the cucumber project in Github due to build failures. I'm trying to fix this but have hit a brick wall and I'm seeking your guidance to get it resolved. Details below.

 

The current cucumber-gosu implementation depends on classes in gw.lang.launch.* that have been removed since gosu-core-1.4. Therefore the solution implemented in a couple of years back is now broken. The cucumber-gosu implementation needs to be refactored to work with the latest gosu core libraries. I'm using gosu-core-1.13.4 for my testing purposes.


Broken code:

https://github.com/cucumber/cucumber-jvm/blob/master/gosu/src/main/java/cucumber/runtime/gosu/GosuBackend.java#L30

        

@Override 

 

public void loadGlue(Glue glue, List<String> gluePaths) {

 

this.glue = glue;

 

GlueSource source = new GlueSource();

 

for (String gluePath : gluePaths) {

 

for (Resource glueScript : resourceLoader.resources(gluePath, ".gsp")) {

 

source.addGlueScript(glueScript);

 

}

 

}

 

Gosu gosu = new Gosu();

 

gosu.start(source.toArgInfo());

 

}


Fix:

I've forked the repo and refactored the code to evaluate gsp files using gw.lang.reflect.ReflectUtil as shown below (a simplified version with hard coded gsp name).

 

var clazz:Class=ReflectUtil.getClass("cucumber.features.MyStepDefinitions").getBackingClass()

((IProgramInstance)(clazz.newInstance())).evaluate(null);

 

This works fine if I run this on a Gosu Scratchpad (in IntelliJ) but fails with the following exception when executed from the cucumber-gosu library. Based on my investigation it fails to find the above class in the classpath under the _default_ module.



Exception in thread "main" java.lang.reflect.InvocationTargetException

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:498)

at com.intellij.rt.execution.CommandLineWrapper.main(CommandLineWrapper.java:46)

Caused by: gw.internal.gosu.parser.RuntimeExceptionWithNoStacktrace: java.lang.ClassNotFoundException: cucumber.features.MyStepDefinitions in module _default_

Caused by: java.lang.ClassNotFoundException: cucumber.features.MyStepDefinitions in module _default_

at gw.internal.gosu.parser.TypeLoaderAccess.getIntrinsicTypeByFullName(TypeLoaderAccess.java:366)

at gw.internal.gosu.parser.TypeLoaderAccess.getByFullName(TypeLoaderAccess.java:959)

at gw.lang.reflect.TypeSystem.getByFullName(TypeSystem.java:154)

at gw.lang.reflect.ReflectUtil.getClass(ReflectUtil.java:156)

at gw.lang.reflect.ReflectUtil.getClassButThrowIfInvalid(ReflectUtil.java:161)

at cucumber.runtime.gosu.GosuBackend.runGlueScript(GosuBackend.java:47)      <-- this is where I call ReflectUtil.getClass(…)

at cucumber.runtime.gosu.GosuBackend.loadGlue(GosuBackend.java:42)

at cucumber.runtime.Runtime.<init>(Runtime.java:90)

at cucumber.runtime.Runtime.<init>(Runtime.java:68)

at cucumber.runtime.Runtime.<init>(Runtime.java:64)

at cucumber.api.cli.Main.run(Main.java:35)

at cucumber.api.cli.Main.main(Main.java:18)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:498)

at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

at java.lang.reflect.Method.invoke(Method.java:498)

at com.intellij.rt.execution.CommandLineWrapper.main(CommandLineWrapper.java:46)

 


If someone can show me a way to resolve this issue or a different approach to evaluate the gsp files at runtime, I would really appreciate. If you would like to know more details about the above code, please let me now.

Many thanks! 

Samarak


Kyle Moore

unread,
Dec 12, 2016, 3:42:35 PM12/12/16
to gosu...@googlegroups.com
Hi Samarak; I'll try to have a look at this.

Can you please share a link to your fork(s) and the steps to reproduce the issue?

--
You received this message because you are subscribed to the Google Groups "gosu-lang" group.
To unsubscribe from this group and stop receiving emails from it, send an email to gosu-lang+unsubscribe@googlegroups.com.

To post to this group, send email to gosu...@googlegroups.com.

samarak

unread,
Dec 12, 2016, 7:10:34 PM12/12/16
to gosu-lang
Thanks Kyle.


I'm using Guidewire Studio for my tests and have the following files and dependencies loaded.

Cucumber test files attached (cucumber-test-feature). Place these directly under your gsrc or gtest directories.
Following screenshot shows the dependencies I've added to the project in GW Studio (IntelliJ). Note that the highlighted jar is the one I built from my fork.
Then you right click on the .feature file (in zip file) and run.


Cheers!
Samarak
To unsubscribe from this group and stop receiving emails from it, send an email to gosu-lang+...@googlegroups.com.

Kyle Moore

unread,
Dec 13, 2016, 1:35:23 AM12/13/16
to gosu-lang
OK, thanks for sharing. Stay tuned; I think I have a fix/workaround.

Kyle Moore

unread,
Dec 13, 2016, 9:02:30 PM12/13/16
to gosu-lang
Hi Samarak. I submitted a PR to cucumber-jvm here: https://github.com/cucumber/cucumber-jvm/pull/1086

Your solution using ReflectUtil was really clever. The missing piece was the call to GosuClassPathThing#init. This performs the minimal Gosu initialization that was missing when you invoked the code from outside the IDE.

Since Gosu's runtime now requires Java 8, it's difficult to make all of cucumber's tests pass on TravisCI - they also run on openjdk7 and oraclejdk7. 

In the meantime, however, you are welcome to apply the diff from the PR commit to your local copy and build and test it. 

Good luck and thanks for fixing this!

- Kyle

Kaushalya Samarasekera

unread,
Dec 14, 2016, 3:58:31 AM12/14/16
to gosu...@googlegroups.com
Hi Kyle,

This is fantastic. Thanks for resolving this so swiftly.

I cloned your fix_gosu branch and tested it. I can see now it's building successfully and cucumber-gosu feature tests are passing. Sweet!

However when I ran the cucmber test  I have in Guidewire studio with the new cucumber-gosu jar, it failed due to the same exception (class not found in module _default_). It should be something to do with how the test runner is contracting the classpath. I have to park this for the moment but planing to revisit as soon as I find a time over the next few weeks for a closer look.

Thanks again for your help in fixing this.


--
You received this message because you are subscribed to a topic in the Google Groups "gosu-lang" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/gosu-lang/yMJnzQwuFpo/unsubscribe.
To unsubscribe from this group and all its topics, send an email to gosu-lang+unsubscribe@googlegroups.com.

samarak

unread,
Dec 14, 2016, 10:13:44 PM12/14/16
to gosu-lang
Hi Kyle,

I managed to run this in Guidewire Studio as well. All I had to do was add the following class under the same package and set -Didea.force.junit3=false in its run configuration. This allows @RunWith(Cucumber) to run correctly.

package cucumber.features

uses cucumber.api.junit.Cucumber
uses org.junit.runner.RunWith

/**
* Created by Kaushalya Samarasekera on 14/12/2016.
*/
@RunWith(Cucumber)
class RunCucumber {

}

All working fine with the fixed cucumber-gosu.

Cheers!
Reply all
Reply to author
Forward
0 new messages