Code Coverage for Xtend

January 12, 2016 2 comments

This is a short blog on how to do code coverage reports with Xtend.
Imagine you have some nice Xtend Code like this one

package demo

class Demo {

    def doSomething() {
        return 1
    }

    def doSomethingElse() {
        return 2
    }

}

wouldn’t it be nice to have code coverage like

Bildschirmfoto 2016-01-12 um 23.57.44

Using the xtend-maven-plugin in version 2.9.0+ this works more or less out of the box using

<plugin>
	<groupId>org.eclipse.xtend</groupId>
	<artifactId>xtend-maven-plugin</artifactId>
	<version>2.9.0</version>
	<executions>
		<execution>
			<goals>
				<goal>compile</goal>
				<goal>testCompile</goal>
				<goal>xtend-install-debug-info</goal>
				<goal>xtend-test-install-debug-info</goal>
			</goals>
			<configuration>
				<xtendAsPrimaryDebugSource>true</xtendAsPrimaryDebugSource>
				<outputDirectory>${project.build.directory}/xtend-gen/main</outputDirectory>
				<testOutputDirectory>${project.build.directory}/xtend-gen/test</testOutputDirectory>
				<writeTraceFiles>true</writeTraceFiles>
			</configuration>
		</execution>
	</executions>
</plugin>

The interesting (new) parts are the goals xtend-install-debug-info and xtend-test-install-debug-info as well as the configuration xtendAsPrimaryDebugSource.

Here is my complete sample pom

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>demo</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>
	<build>
		<plugins>


			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<compilerId>eclipse</compilerId>
					<source>1.8</source>
					<target>1.8</target>
					<optimize>true</optimize>

				</configuration>
				<dependencies>
					<dependency>
						<groupId>org.codehaus.plexus</groupId>
						<artifactId>plexus-compiler-eclipse</artifactId>
						<version>2.6</version>
						<scope>runtime</scope>
					</dependency>
				</dependencies>
			</plugin>


			<plugin>
				<groupId>org.jacoco</groupId>
				<artifactId>jacoco-maven-plugin</artifactId>
				<version>0.7.5.201505241946</version>


				<executions>
					<!-- Prepares the property pointing to the JaCoCo runtime agent which 
						is passed as VM argument when Maven the Surefire plugin is executed. -->
					<execution>
						<id>pre-unit-test</id>
						<goals>
							<goal>prepare-agent</goal>
						</goals>
						<configuration>
							<!-- Sets the path to the file which contains the execution data. -->
							<destFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</destFile>
							<!-- Sets the name of the property containing the settings for JaCoCo 
								runtime agent. -->
							<propertyName>surefireArgLine</propertyName>

						</configuration>
					</execution>
					<!-- Ensures that the code coverage report for unit tests is created 
						after unit tests have been run. -->
					<execution>
						<id>post-unit-test</id>
						<phase>test</phase>
						<goals>
							<goal>report</goal>
						</goals>
						<configuration>

							<!-- Sets the path to the file which contains the execution data. -->
							<dataFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</dataFile>
							<!-- Sets the output directory for the code coverage report. -->
							<outputDirectory>${project.reporting.outputDirectory}/jacoco-ut</outputDirectory>
						</configuration>
					</execution>
				</executions>

			</plugin>

			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-surefire-plugin</artifactId>
				<version>2.15</version>
				<configuration>
					<!-- Sets the VM argument line used when unit tests are run. -->
					<argLine>${surefireArgLine}</argLine>
					<!-- Skips unit tests if the value of skip.unit.tests property is true -->
					<skipTests>${skip.unit.tests}</skipTests>
					<!-- Excludes integration tests when unit tests are run. -->
					<excludes>
						<exclude>**/IT*.java</exclude>
					</excludes>
				</configuration>
			</plugin>

			<plugin>
				<groupId>org.eclipse.xtend</groupId>
				<artifactId>xtend-maven-plugin</artifactId>
				<version>2.9.0</version>
				<executions>
					<execution>
						<goals>
							<goal>compile</goal>
							<goal>testCompile</goal>
							<goal>xtend-install-debug-info</goal>
							<goal>xtend-test-install-debug-info</goal>
						</goals>
						<configuration>
							<xtendAsPrimaryDebugSource>true</xtendAsPrimaryDebugSource>
							<outputDirectory>${project.build.directory}/xtend-gen/main</outputDirectory>
							<testOutputDirectory>${project.build.directory}/xtend-gen/test</testOutputDirectory>
							<writeTraceFiles>true</writeTraceFiles>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>

	<dependencies>
		<dependency>
			<groupId>org.eclipse.xtend</groupId>
			<artifactId>org.eclipse.xtend.lib</artifactId>
			<version>2.9.0</version>
		</dependency>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.11</version>
		</dependency>

	</dependencies>
</project>
Categories: Xtext

Xtext and Strings as Cross-References

Cross References are a often used concept in Xtext. They ususally work like this

Model:
    definitions+=Definition*
    usages+=Usage*
;

Definition:
    "define" name=ID
;

Usage:
   "use" definition=[Definition]
;

They can be used in the model like this

define Thing
use Thing

but what if i want to write something like

define "This is a Thing"
use "This is a Thing"

well the definition part is easily changed

Definition:
    "define" name=STRING
;

But what about the usage part?
well it is quite easy as well. refName=[Type] is short for refName=[Type|ID] which means ‘Refererence a Type and parse an ID. So to use another Terminal or Data Type Rule we change it to refName=[Type|RULENAME]

Usage:
   "use" definition=[Definition|STRING]
;

Now the cross refs are working fine. but if we try the editor we find out what autoedit and content assist disturb each other. We type " and auto edit gets us to "|". If we now type Crtl+Space for content assist we finally get "This is a Thing"" with an extra " at the end.
To avoid this we have to tweak the proposal provider a bit.

package org.xtext.example.mydsl.ui.contentassist

import org.xtext.example.mydsl.ui.contentassist.AbstractMyDslProposalProvider
import org.eclipse.emf.ecore.EObject
import org.eclipse.xtext.Assignment
import org.eclipse.xtext.ui.editor.contentassist.ContentAssistContext
import org.eclipse.xtext.ui.editor.contentassist.ICompletionProposalAcceptor
import org.eclipse.xtext.ui.editor.contentassist.ICompletionProposalAcceptor.Delegate
import org.eclipse.jface.text.contentassist.ICompletionProposal
import org.eclipse.xtext.ui.editor.contentassist.ConfigurableCompletionProposal

class MyDslProposalProvider extends AbstractMyDslProposalProvider {

	override completeUsage_Definition(EObject model, Assignment assignment, ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
		super.completeUsage_Definition(model, assignment, context, new  StringProposalDelegate(acceptor, context))
	}

	static class StringProposalDelegate extends Delegate {

		ContentAssistContext ctx

		new(ICompletionProposalAcceptor delegate, ContentAssistContext ctx) {
			super(delegate)
			this.ctx = ctx
		}

		override accept(ICompletionProposal proposal) {
			if (proposal instanceof ConfigurableCompletionProposal) {
				val endPos = proposal.replacementOffset + proposal.replacementLength 
				if (ctx.document != null && ctx.document.length > endPos) {
					// We are not at the end of the file
					if ("\"" == ctx.document.get(endPos, 1)) {
						proposal.replacementLength = proposal.replacementLength-1
						proposal.replacementString = proposal.replacementString.substring(0,proposal.replacementString.length-1)
					}
				}
			}
			super.accept(proposal)
		}

	}

}

what we basically do is detecting the situation and remove the extra "

Use of EcoreGenerator to customize EMFs generated Java Classes

This is a blog post on the usage of the org.eclipse.emf.mwe2.ecore.EcoreGenerator worflow component.
It is an alternative to EMFs JMerge Based approach to customize generated Java Classes from an ecore model.

So let us asume we have the following Ecore file
(/sample/model/sample.ecore)

<?xml version="1.0" encoding="UTF-8"?>
<ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" name="sample" nsURI="http://www.eclipse.org/xtext/ecore/sample" nsPrefix="sample">
  <eClassifiers xsi:type="ecore:EClass" name="Person">
    <eOperations name="getFullname" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
    <eStructuralFeatures xsi:type="ecore:EAttribute" name="firstname" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
    <eStructuralFeatures xsi:type="ecore:EAttribute" name="lastname" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
  </eClassifiers>
</ecore:EPackage>

with the following genmodel
(/sample/model/sample.genmodel)

<?xml version="1.0" encoding="UTF-8"?>
<genmodel:GenModel xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore"
    xmlns:genmodel="http://www.eclipse.org/emf/2002/GenModel" modelDirectory="/sample/src-gen" modelPluginID="sample" modelName="Sample"
    importerID="org.eclipse.emf.importer.ecore" complianceLevel="6.0" copyrightFields="false">
  <foreignModel>sample.ecore</foreignModel>
  <genPackages prefix="Sample" disposableProviderFactory="true" ecorePackage="sample.ecore#/">
    <genClasses ecoreClass="sample.ecore#//Person">
      <genFeatures createChild="false" ecoreFeature="ecore:EAttribute sample.ecore#//Person/firstname"/>
      <genFeatures createChild="false" ecoreFeature="ecore:EAttribute sample.ecore#//Person/lastname"/>
      <genOperations ecoreOperation="sample.ecore#//Person/getFullname"/>
    </genClasses>
  </genPackages>
</genmodel:GenModel>

I have changed the modelDirectory="/sample/src-gen"

Now we want to implement the getFullname() method.
we would usually implement the code in PersonImpl and change the javadoc to @generated NOT or remove the @generated.
(and checkin the generated code as well)

But let us try another approach

so let us create following class

(/sample/src/sample/impl/PersonImplCustom.java)

package sample.impl;

import sample.impl.PersonImpl;

public class PersonImplCustom extends PersonImpl {
	
	@Override
	public String getFullname() {
		return getFirstname() + " " + getLastname();
	}

}

and workflow

(/sample/src/test.mwe2)

module test

Workflow {
	
	bean = org.eclipse.emf.mwe.utils.StandaloneSetup {
		platformUri = ".."
	}
	
	component = org.eclipse.emf.mwe.utils.DirectoryCleaner {
		directory = "src-gen"
	}
	
	component = org.eclipse.emf.mwe2.ecore.EcoreGenerator {
		generateCustomClasses = false
		genModel = "platform:/resource/sample/model/sample.genmodel"
		srcPath = "platform:/resource/sample/src"	
	}
	
}

here is the Manifest for the deps
(/sample/META-INF/MANIFEST.MF)

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: sample
Bundle-SymbolicName: sample; singleton:=true
Bundle-Version: 0.1.0
Require-Bundle: org.eclipse.emf.mwe2.lib,
 org.eclipse.emf.mwe2.launch,
 org.apache.commons.logging,
 org.apache.commons.lang,
 org.eclipse.xtext.xbase.lib,
 org.eclipse.xtext,
 org.eclipse.xtext.generator,
 org.apache.commons.logging,
 org.eclipse.ui.workbench,
 org.eclipse.core.runtime,
 org.eclipse.core.commands,
 org.eclipse.xtext.ui,
 org.eclipse.core.expressions,
 org.apache.log4j
Export-Package: sample.impl,
 sample.util

If we run the workflow emf is configured to take PersonImplCustom instead of PersonImpl so that the following will work nicely

public class Test {
	
	public static void main(String[] args) {
		Person p = SampleFactory.eINSTANCE.createPerson();
		p.setFirstname("Christian");
		p.setLastname("Dietrich");
		System.out.println(p.getFullname());
	}

}
Categories: Xtext

Xtext and Dot/Path-Expressions

If you have a DSL that describes structure (e.g. like an Entity DSL) you often need to “walk” on this structure using dot/path-expressions.

Let us asume we have a grammar like

Model:
	entities+=Entity*
;

Entity:
	"entity" name=ID "{"
		features+=Feature*
	"}"
;

Feature:
	Attribute | Reference
;

Attribute:
	"attr" name=ID ":" type=DataType
;

enum DataType:
	string | int
;

Reference:
	"ref" name=ID ":" type=[Entity]
;

and a model like

entity A {
	attr a1 : int
	attr a2 : string
	ref b : B
	ref c : C
}

entity B {
	attr b1 : string
	attr b2 : string
	ref a : A
	ref c : C
}

entity C {
	attr c1 : string
	attr c2 : int
}

and want to have expressions like

use A.b.b2
use A.b.c.c1
use A.a1
use A.b.a.a1

but how to do this with Xtext?
there are several possibility but the following was working well in my usecase:

Model:
	entities+=Entity*
	usages+=Usage*
;
Usage:
	"use" ref=DotExpression
;

DotExpression returns Ref:
	EntityRef ({DotExpression.ref=current}  "." tail=[Feature])*
;

EntityRef returns Ref:
	{EntityRef} entity=[Entity]
; 

a bit of scoping

class MyDslScopeProvider extends AbstractDeclarativeScopeProvider {

	def IScope scope_DotExpression_tail(DotExpression exp, EReference ref) {
		val head = exp.ref;
		switch (head) {
			EntityRef : Scopes::scopeFor(head.entity.features)
			DotExpression : {
				val tail = head.tail
				switch (tail) {
					Attribute : IScope::NULLSCOPE
					Reference : Scopes::scopeFor(tail.type.features)
					default: IScope::NULLSCOPE
				}
			}
			
			default: IScope::NULLSCOPE
		}
	}

}

and it works fine.

as an additional note: the ast of the expressions look like

Sample AST

Sample AST

Categories: Xtext Tags: , ,

Xtext Model Visualization with PlantUML

April 12, 2013 1 comment

One of my Colleagues recently gave me a hint on PlantUML which is a nice tool to create Graphviz based UML diagrams from a textual input. This blogpost describes how to include PlantUML into Xtext to generate Visualizations from textual models on the fly.

Here is the DSL from Xtexts 15 minutes example

Domainmodel :
  elements += Type*
;
  
Type:
  DataType | Entity
;
  
DataType:
  'datatype' name = ID
;
 
Entity:
  'entity' name = ID ('extends' superType = [Entity])? '{'
     features += Feature*
  '}'
;
 
Feature:
  many?='many'? name = ID ':' type = [Type]
;

The target is to take an input model like

datatype String

entity A {
	many names : String
	c : C
}

entity B {
	something : String
	many myA : A
}

entity C {
	
}

An generate a nice Diagram like

test.mydsl

To make the integration easy we generate the png using the existing Builder/Generator infrastructure

So here is the text input for PlantUML we need to generate

 @startuml
class A {
	List<String> names
}

A o--  C : c

class B {
	String something
}

B o-- "*"  A : myA

class C {
}


@enduml

and here the generator that does the conversion and feeds Plantuml

class MyDslGenerator implements IGenerator {

	override void doGenerate(Resource resource, IFileSystemAccess fsa) {
		val filename = resource.URI.lastSegment
		for (dm : resource.contents.filter(typeof(Domainmodel))) {
			val plantUML = dm.toPlantUML.toString
			if (fsa instanceof IFileSystemAccessExtension3) {
				val out = new ByteArrayOutputStream()
				new SourceStringReader(plantUML).generateImage(out)
				(fsa as IFileSystemAccessExtension3).generateFile(filename + ".png",
					new ByteArrayInputStream(out.toByteArray))
			} else {
				fsa.generateFile(filename + ".txt", plantUML)
			}
		}
	}

	
	def dispatch CharSequence toPlantUML(Domainmodel it) '''
	@startuml
	«FOR e : elements.filter(typeof(Entity))»
	«e.toPlantUML»
	«ENDFOR»
	@enduml
	'''
	
	def dispatch CharSequence toPlantUML(Entity it) '''
	class «name» {
		«FOR f : features.filter[type instanceof DataType]»
		«IF f.many»List<«f.type.name»>«ELSE»«f.type.name»«ENDIF» «f.name»
		«ENDFOR»
	}
	
	«FOR f : features.filter[type instanceof Entity]»
		«name» o-- «IF f.many»"*" «ENDIF» «f.type.name» : «f.name»
	«ENDFOR»
	
	'''	
}

To get PlantUML into the Classpath we add the jar to the project
and add it via the Manifest.MF file

Bundle-ClassPath: .,
 lib/plantuml.jar
Categories: Xtext

Xtext: Referencing Elements of one DSL from another DSL

This is a blog post on Inter-Language-Cross-References in Xtext. Let us asume we have a DSL that contains Definitions. Now we want to create another DSL that references (uses) the Definitions defined in the the first DSL.

So let us start with the first DSL: We create a new Xtext Project

And here is the (for demonstration purpose oversimplyfied) Grammar

grammar org.xtext.example.definitions.Definitions with org.eclipse.xtext.common.Terminals

generate definitions "http://www.xtext.org/example/definitions/Definitions"

Model:
	definitions+=Definition*;

Definition:
	'define' name=ID;

We run GenerateDefinitions.mwe2 to generate the language.
This is all for the first DSL.

Now let us create a project for the second DSL.

To be able to reference the first DSL from the second we add a bundle dependency to the Manifest of the second

Here the resulting manifest

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: org.xtext.example.usages
Bundle-Vendor: My Company
Bundle-Version: 1.0.0.qualifier
Bundle-SymbolicName: org.xtext.example.usages; singleton:=true
Bundle-ActivationPolicy: lazy
Require-Bundle: org.eclipse.xtext;visibility:=reexport,
 org.eclipse.xtext.xbase;resolution:=optional;visibility:=reexport,
 org.eclipse.xtext.generator;resolution:=optional,
 org.apache.commons.logging;bundle-version="1.0.4";resolution:=optional,
 org.eclipse.emf.codegen.ecore;resolution:=optional,
 org.eclipse.emf.mwe.utils;resolution:=optional,
 org.eclipse.emf.mwe2.launch;resolution:=optional,
 org.xtext.example.definitions;bundle-version="1.0.0"
Import-Package: org.apache.log4j
Bundle-RequiredExecutionEnvironment: J2SE-1.5

Now we can create the Grammar from the Usage DSL

grammar org.xtext.example.usages.Usages with org.eclipse.xtext.common.Terminals

generate usages "http://www.xtext.org/example/usages/Usages"

import "http://www.xtext.org/example/definitions/Definitions" as def

Model:
	usages+=Usage*;
	
Usage:
	'use' definition=[def::Definition];

With "http://www.xtext.org/example/definitions/Definitions" as def we import the metamodel of our Define DSL to our Usage DSL.
Thus the Type Definition is available and can be used to define the cross reference definition=[def::Definition]

To get the thing running we have to do some adjustments to the workflow of the Usages language.

module org.xtext.example.usages.GenerateUsages

import org.eclipse.emf.mwe.utils.*
import org.eclipse.xtext.generator.*
import org.eclipse.xtext.ui.generator.*

var grammarURI = "classpath:/org/xtext/example/usages/Usages.xtext"
var file.extensions = "use"
var projectName = "org.xtext.example.usages"
var runtimeProject = "../${projectName}"

Workflow {
    bean = StandaloneSetup {
        scanClassPath = true
        platformUri = "${runtimeProject}/..";
        registerGeneratedEPackage = "org.xtext.example.definitions.definitions.DefinitionsPackage"
        registerGenModelFile = "platform:/resource/org.xtext.example.definitions/src-gen/org/xtext/example/definitions/Definitions.genmodel"
    }
    
        ...
}

we generate our Usage DSL (GenerateUsages.mwe2)

finally we start a new runtime eclipse and give it a try

Update Xtext 2.9.2+:

Here is how the workflow looks like with the new Xtext 2.9.x generator

module org.xtext.example.usages.GenerateUsages

import org.eclipse.xtext.xtext.generator.*
import org.eclipse.xtext.xtext.generator.model.project.*

var rootPath = ".."

Workflow {
	
	component = XtextGenerator {
		configuration = {
			project = StandardProjectConfig {
				baseName = "org.xtext.example.usages"
				rootPath = rootPath
				runtimeTest = {
					enabled = true
				}
				eclipsePlugin = {
					enabled = true
				}
				eclipsePluginTest = {
					enabled = true
				}
				createEclipseMetaData = true
			}
			code = {
				encoding = "UTF-8"
				fileHeader = "/*\n * generated by Xtext \${version}\n */"
			}
		}
		language = StandardLanguage {
			name = "org.xtext.example.usages.Usages"
			referencedResource = "platform:/resource/org.xtext.example.definitions/model/generated/Definitions.genmodel"
			fileExtensions = "use"

			serializer = {
				generateStub = false
			}
			validator = {
			}
		}
	}
}

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.

Categories: Xtext