Home > Xtext > Unittesting Xtend Generators

Unittesting Xtend Generators

Xtext offers nice Support for Unit Tests. But how to test a Xtend based Generator? This blogpost describes a simple approach for such a Test.

So let us take Xtext’s Hello World grammar as Starting point

Model:
	greetings+=Greeting*;
	
Greeting:
	'Hello' name=ID '!';

And following simple Generator

package org.xtext.example.mydsl.generator

import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.generator.IFileSystemAccess
import org.eclipse.xtext.generator.IGenerator
import org.xtext.example.mydsl.myDsl.Greeting

class MyDslGenerator implements IGenerator {
	
	override void doGenerate(Resource resource, IFileSystemAccess fsa) {
		for (g : resource.allContents.toIterable.filter(typeof(Greeting))) {
			fsa.generateFile(g.name+".java", 
			'''
			public class «g.name» {
				
			}
			''')
		}
	}
}

And here the Test

import org.junit.Test
import org.junit.runner.RunWith
import org.eclipse.xtext.junit4.XtextRunner
import org.eclipse.xtext.junit4.InjectWith
import org.xtext.example.mydsl.MyDslInjectorProvider
import org.eclipse.xtext.generator.IGenerator
import com.google.inject.Inject
import org.eclipse.xtext.junit4.util.ParseHelper
import org.xtext.example.mydsl.myDsl.Model
import org.eclipse.xtext.generator.InMemoryFileSystemAccess

import static org.junit.Assert.*
import org.eclipse.xtext.generator.IFileSystemAccess

@RunWith(typeof(XtextRunner))
@InjectWith(typeof(MyDslInjectorProvider))
class GeneratorTest {
	
	@Inject IGenerator underTest
	@Inject ParseHelper<Model> parseHelper 
	
	@Test
	def test() {
		val model = parseHelper.parse('''
		Hello Alice!
		Hello Bob!
		''')
		val fsa = new InMemoryFileSystemAccess()
		underTest.doGenerate(model.eResource, fsa)
		println(fsa.files)
		assertEquals(2,fsa.files.size)
		assertTrue(fsa.files.containsKey(IFileSystemAccess::DEFAULT_OUTPUT+"Alice.java"))
		assertEquals(
			'''
			public class Alice {
				
			}
			'''.toString, fsa.files.get(IFileSystemAccess::DEFAULT_OUTPUT+"Alice.java").toString
		)
		assertTrue(fsa.files.containsKey(IFileSystemAccess::DEFAULT_OUTPUT+"Bob.java"))
		assertEquals(
			'''
			public class Bob {
				
			}
			'''.toString, fsa.files.get(IFileSystemAccess::DEFAULT_OUTPUT+"Bob.java").toString)
		
	}
	
}

But how does that work?

Xtext offers a specific org.junit.runner.Runner. For Junit4 it is org.junit.runner.Runner. This Runner allows in combination with a
org.eclipse.xtext.junit4.IInjectorProvider language specific injections within the test.
Since we have fragment = junit.Junit4Fragment {} in our workflow
Xtext already Generated the Class org.xtext.example.mydsl.MyDslInjectorProvider.
If we would not use Xtext at all we would have to create such a InjectorProvider manually.

To wire these things up we annotate your Test with @RunWith(typeof(XtextRunner)) and @InjectWith(typeof(MyDslInjectorProvider))

Now we can write our Test. This Basically consists of 3 steps
(1) read a model
(2) call the Generator
(3) Capture the Result

We solve Step (1) using Xtext’s org.eclipse.xtext.junit4.util.ParseHelper and Step (3) by using a special kind of IFileSystemAccess that keeps the files InMemory and does not write them to the disk.

I hope this gives you a start writing you Xtext/Xtend Generator Tests.

Advertisements
Categories: Xtext
  1. holgerschill
    May 8, 2012 at 19:39

    Thanks Christian.

  2. Sebastian Zarnekow
    May 8, 2012 at 19:50

    Thanks for the comprehensive overview, Christian.
    With Xtext 2.3 it becomes even simpler for languages that use the Xbase expressions. The CompilationTestHelper from org.eclipse.xtext.xbase.compiler encapsulates a great bunch of the stuff that you had to do by yourself in your test code.

  3. May 11, 2012 at 18:58

    I am unsure if testing the generator output is a good idea. We have some of these tests in Spray, and they tend to break often, since templates change rather often. I would only test small pieces of the generator in that way. Most logic is normally in the Xtend functions that are used by the generator, I would test them more intensive. But anyway, if you would like to test it such, your approach is the right.

    • May 11, 2012 at 21:42

      Hi,

      guess this highly depends on what you want to achieve: if you have some extensions with some certain (business) logic it of course makes sense to write unit tests for them. never the less one might do some integration tests as well. especially in early phases of development when you might do bigger refactorings to the generator i find it easier to go with integration tests as with unit tests (one reason might be that refactoring support in Xtend is not yet that good as it is in JDT)

      i just wanted to give some hints/a starting on how to do such a test and how to test with Xtext in general.

      Regards, Christian

      • May 14, 2012 at 05:21

        Hi Christian! Sure, this was no criticism. It really depends on the case. And often the problem is that just too few is tested, and especially the code generator not.
        ~Karsten

  4. July 24, 2012 at 14:47

    Hi Christian, thanks for this post. Is there a simple way to enhance this test if you have to add another resource to the ResourceSet before testing the code generation, e.g. if there are links from the DSL I want to test to another DSL?

  5. July 25, 2012 at 09:48

    Christian, thanks for answering so fast. In my case I use two different DSLs and have to inject two ParseHelpers: one for ModelA of DSL A and one for ModelB of DSL B. If I am currently testing DSL B how can I inject the ParseHelper for DSL A?

    • July 25, 2012 at 19:45

      Hmmm i think you need an InjectorProvider that inits both languages

      public class ExtendedMyDslBInjectorProvider extends MyDslBInjectorProvider {

      @Override
      protected Injector internalCreateInjector() {
      MyDslAStandaloneSetup.doSetup();
      return super.internalCreateInjector();
      }

      }

      then you need to fix the parsehelper (a bugzilla for that would be nice)

      public class ParseHelper2 extends ParseHelper {

      @Override
      public T parse(InputStream in, URI uriToUse, Map options,
      ResourceSet resourceSet) {
      Resource resource = resourceSet.createResource(uriToUse);
      resourceSet.getResources().add(resource);
      try {
      resource.load(in, options);
      final T root = (T) (resource.getContents().isEmpty() ? null : resource.getContents().get(0));
      return root;
      } catch (IOException e) {
      throw new WrappedException(e);
      }
      }

      }

      • July 31, 2012 at 09:51

        Thanks, Christian, for your reply. It is working fine and I added your solution to Bugzilla (Bug 386302).

  6. July 27, 2012 at 06:34

    FYI This doesn’t natively work in windows because of the carriage return character. I had to append

    .replaceAll(“[\r]”, “”) to line 38 and 46 after each toString method call to get the assertion to not fail.

    • July 27, 2012 at 06:39

      Hi if you use the system specific line breaks in the test file as well it should work anyway. At least in Xtext 2.3.0. Before it was a kind of hard coded \n

  7. hq
    November 30, 2012 at 15:14

    Hi Christian, very interesting post. Got one question: I’m using xtext 2.3.1 and the statement “underTest.doGenerate(model.eResource, fsa)” seems cannot be resolved by xtend. It complains the doGenerate() cannot be resolved. What is working is to change as followings:
    val IGenerator g = undertest
    g.doGenerate(model.eResource, fsa)
    Could you give me short info why?

    Thanks, hq

    • November 30, 2012 at 15:19

      Hi,

      are you sure the imports are right?
      i dont know of any api changes at IGenerator in 2.3.1
      maybe this i a bug in the xtend itself.

  8. Anton
    February 6, 2014 at 16:15

    Hi Christian,
    I also want to write a test where I use two different DSLs.
    I implemented an ExtendedDslInjectorProvider as you suggested and have two CharSequences – each for the specifid DSLs – which I parsed with two ParseHelper-classes.
    But when I access the resourceSet there is no connection between the two DSLs. The referring DSL only has Proxy-Objects for its counterpart. I can not navigate between them.

    For example in the “Application-DSL”:
    In the entity-Object I only see a Proxy and I can not get to the entityAttributes. The Set is returned empty.

    val resource = resourceSet.getResource(URI.createURI(“test.platform”), true);
    val app = resource.getContents().get(0) as Application
    val entity = app.productCategory.head.entity
    val entityAttributes = entity.attributes;

    • February 6, 2014 at 16:23

      Hi if the resource exists and you call resource.load it should work perfectly. If not please share a complete runnable reproduceable example

  9. Anton
    February 6, 2014 at 17:49

    Hi Christian,
    thanks for the fast answer. I already call the resource.load-Method in the “ExtendedParseHelper”-class which I copied from your post with the “ParseHelper2”. But this does have not the desired effect.

    I published the Test-Project under following link. Hopefully you can take a look?
    https://www.dropbox.com/sh/d5kfbet7tjzztt4/xc_h5H7G2V

    • Anton
      February 6, 2014 at 17:51

      The class where I try to connect the two DSLs is the class “ProductCategoryServiceTest”

      • Anton
        February 6, 2014 at 18:48

        I found the reason… the CharSequence of one DSL was not valid. :-/

  10. Kunal Khaware
    February 10, 2014 at 08:40

    Hi Chris,
    How to write Junit test for a grammar which has reference to another grammar .below is a sample example for a grammar

    generate ad “http://www.xtext.org/example/mydsl/MyDsl”
    import “http://www.xtext.org/example/mydsl/AdSym” as adsym
    Model:
    greetings+=Greeting*;

    Greeting:
    ‘Hello’ name=ID ‘!’;

    =====================================

    My Problem is when I try to write Junit for above model assertNotError of ValidationTestHelper does not recognise the rules imported by AdSym.

    Please help here 🙂

    Cheers

    Kunal

    • February 11, 2014 at 06:51

      Hi,

      can you please elaborate what you mean by “does not recognize”?

      • Kunal Khaware
        February 11, 2014 at 08:28

        I mean when I say “does not recognize is that” when I try to test Model using Junit like below code :
        ===================================================
        @RunWith(typeof(XtextRunner))
        @InjectWith(typeof (MyDslInjectorProvider))
        class MyDslParserTest{
        @Inject extension ParseHelper modelParserHelper
        @Inject extension ValidationTestHelper

        @Test
        def testMyDslParser(){
        var model=modelParserHelper.parse(”’

        Hello Test!!
        viewName:Player
        ”’)
        model.assertNoErrors()

        }

        }

        ==========================================
        I get below result when I run above Junit :

        java.lang.AssertionError: Expected no errors, but got :ERROR (org.eclipse.xtext.diagnostics.Diagnostic.Linking) ‘Couldn’t resolve reference to ViewType ‘Player’.’ on ViewDefinition

        =========================================
        here View Name is Grammar that has been imported from other grammar file AdSym.

        The problem is in such scenario, how do I write Junit to test grammar included by other grammars also .

        Cheers

        Kunal

    • February 11, 2014 at 06:55

      P.S: if you actually want to read files of the imported stuff you have to
      adapt org.xtext.example.mydsl.MyDslInjectorProvider.internalCreateInjector()
      to call OtherDslStandaloneSetup.doSetup before returning the actual injector.

      and you have to add all relevant resources to the resourceset involved (if you have multiple).

  11. Sebastian Naefe
    February 11, 2014 at 09:12

    Hi,

    I am also trying to write unit tests including a dependent DSL. The solution described using a modified InjectorProvider and a modified ParseHelper does not work for me. The overridden parse method in the ParseHelper still just returns one DSL model (T), and I need access to both. Can someone maybe elaborate on this solution?

    Cheers,
    Sebastian

    • February 11, 2014 at 10:49

      Please create a thread in the Xtext forum with all necessary stuff attached

      • Sebastian Naefe
        February 12, 2014 at 05:48

        Thanks, Christian, will do.

  12. Andreas Horst
    July 23, 2014 at 13:32

    Thanks for the post! I have a question regarding resource sets in IGenerator unit tests. In my generator I am doing some analysis involving the validation of referenced resources. For this I am using IResourceDescriptions (injected) in my IGenerator implementation; actually only for getting the IResourceDescription of a Resource. However if executed from within the unit test, the context (resourceSet) of the injected IResourceDescriptions is never set and hence null.

    Is this a bad approach in general or is there a simple trick/configuration to make it work in unit tests?

    For now I use a hack in my generator, actually casting down to the actual implementation of IResourceDescriptions (ResourceSetBasedResourceDescriptions) and manually setting the context if not already set; but this seems to be very hacky and something you definitely do not want in production code.

    • July 23, 2014 at 14:16

      Did you try to inject and query a ResourceDescriptionsProvider instead

  13. Andreas Horst
    July 23, 2014 at 16:39

    That did it. Runtime and unit test execution now work consistently. Thank you very much for that quick answer!

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: