/******************************************************************************* * Copyright (c) 2012 BMW Car IT and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html *******************************************************************************/ package org.jnario.jnario.test.util; import static com.google.common.collect.Iterables.filter; import static com.google.common.collect.Iterables.toArray; import static com.google.common.collect.Iterables.transform; import static com.google.common.collect.Lists.newArrayList; import static org.junit.Assert.assertFalse; import static org.eclipse.emf.common.util.URI.createURI; import static org.eclipse.xtext.util.Exceptions.throwUncheckedException; import static org.jnario.jnario.test.util.ResultMatchers.failureCountIs; import static org.jnario.jnario.test.util.ResultMatchers.isSuccessful; import static org.junit.Assert.assertThat; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.Collections; import java.util.List; import java.util.Map; import javax.inject.Singleton; import org.antlr.runtime.Token; import org.eclipse.emf.common.notify.Notifier; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EcorePackage; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.xtend.core.xtend.XtendPackage; import org.eclipse.xtend2.lib.StringConcatenation; import org.eclipse.xtext.Constants; import org.eclipse.xtext.common.types.JvmOperation; import org.eclipse.xtext.common.types.access.impl.ClasspathTypeProvider; import org.eclipse.xtext.common.types.access.impl.IndexedJvmTypeAccess; import org.eclipse.xtext.diagnostics.Severity; import org.eclipse.xtext.generator.JavaIoFileSystemAccess; import org.eclipse.xtext.junit4.IInjectorProvider; import org.eclipse.xtext.junit4.IRegistryConfigurator; import org.eclipse.xtext.junit4.validation.RegisteredValidatorTester; import org.eclipse.xtext.naming.QualifiedName; import org.eclipse.xtext.resource.XtextResourceSet; import org.eclipse.xtext.util.CancelIndicator; import org.eclipse.xtext.util.StringInputStream; import org.eclipse.xtext.util.Strings; import org.eclipse.xtext.validation.CheckMode; import org.eclipse.xtext.validation.IResourceValidator; import org.eclipse.xtext.validation.Issue; import org.eclipse.xtext.xbase.XbasePackage; import org.eclipse.xtext.xbase.compiler.JvmModelGenerator; import org.eclipse.xtext.xbase.lib.Exceptions; import org.eclipse.xtext.xbase.lib.StringExtensions; import org.hamcrest.Matcher; import org.jnario.Assertion; import org.jnario.feature.feature.FeaturePackage; import org.jnario.runner.Named; import org.jnario.spec.spec.SpecPackage; import org.jnario.suite.suite.Suite; import org.junit.Test; import org.junit.experimental.results.PrintableResult; import org.junit.rules.TemporaryFolder; import org.junit.runner.JUnitCore; import org.junit.runner.Result; import org.junit.runner.notification.Failure; import org.mockito.Mockito; import org.objectweb.asm.Type; import testdata.Properties1; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.google.inject.Inject; /** * @author Sebastian Benz - Initial contribution and API * @author Birgit Engelmann */ @SuppressWarnings("restriction") public abstract class BehaviorExecutor { protected static Map<Class<?>, IInjectorProvider> injectorProviderClassCache = Maps.newHashMap(); public static Result execute(Class<? extends IInjectorProvider> injectorType, CharSequence content) { IInjectorProvider injectorProvider = getOrCreateInjectorProvider(injectorType); try { ((IRegistryConfigurator)injectorProvider).setupRegistry(); BehaviorExecutor executor = injectorProvider.getInjector().getInstance(BehaviorExecutor.class); return executor.execute(content); } finally { ((IRegistryConfigurator)injectorProvider).restoreRegistry(); } } public void executesSuccessfully(CharSequence content) { assertThat(execute(content), isSuccessful()); } public void executionFails(CharSequence content) { assertThat(execute(content), failureCountIs(1)); } public Result execute(CharSequence content) { return execute(content, fileExtension); } protected Result execute(CharSequence content, String fileExtension) { Resource resource = parse(content.toString(), fileExtension); Resources.addContainerStateAdapter(resourceSet); Result result = run(resource.getContents().get(0)); return result; } @SuppressWarnings("unchecked") protected static <T extends IInjectorProvider> T getOrCreateInjectorProvider(Class<T> type) { IInjectorProvider injectorProvider = getInjectorProvider(type); if (injectorProvider == null) { injectorProvider = createInjectorProvider(type); injectorProviderClassCache.put(type, injectorProvider); } return (T) injectorProvider; } private XtextResourceSet resourceSet = new XtextResourceSet(); protected Resource parse(CharSequence content) { return parse(content, fileExtension); } private Resource parse(CharSequence content, String file) { installJvmTypeProvider(resourceSet); Resource resource = resourceSet.createResource(createURI("dummy" + resourceSet.getResources().size() +"." + file)); resourceSet.getResources().add(resource); try { resource.load(new StringInputStream(content.toString()), Collections.emptyMap()); // EcoreUtil.resolveAll(resource); Resources.checkForParseErrors(resource); } catch (IOException e) { e.printStackTrace(); org.junit.Assert.fail(e.getMessage()); } return resource; } protected static IInjectorProvider getInjectorProvider(Class<? extends IInjectorProvider> type) { return injectorProviderClassCache.get(type); } protected static IInjectorProvider createInjectorProvider(Class<? extends IInjectorProvider> type) { IInjectorProvider injectorProvider = null; try { injectorProvider = type.newInstance(); } catch (Exception e) { throwUncheckedException(e); } return injectorProvider; } @SuppressWarnings("serial") public static class CompositeResult extends Result{ private List<Result> children = newArrayList(); public void add(Result child) { children.add(child); } @Override public int getFailureCount() { int failureCount = 0; for (Result child : children) { failureCount += child.getFailureCount(); } return failureCount; } @Override public List<Failure> getFailures() { List<Failure> failures = newArrayList(); for (Result child : children) { failures.addAll(child.getFailures()); } return failures; } @Override public int getIgnoreCount() { int ignoreCount = 0; for (Result child : children) { ignoreCount += child.getIgnoreCount(); } return ignoreCount; } @Override public int getRunCount() { int runCount = 0; for (Result child : children) { runCount += child.getRunCount(); } return runCount; } @Override public long getRunTime() { long runtime = 0; for (Result child : children) { runtime += child.getRunTime(); } return runtime; } @Override public String toString() { return Joiner.on("\n").join(transform(children, new Function<Result, PrintableResult>() { public PrintableResult apply(Result result){ return new PrintableResult(result.getFailures()); } })); } } @Inject private JvmModelGenerator generator; @Inject private JavaIoFileSystemAccess fsa; private TemporaryFolder tempFolder = new TemporaryFolder(); @Inject private IResourceValidator validator; protected boolean validate = true; protected FeatureJavaCompiler javaCompiler = FeatureJavaCompiler.getInstance(); @Inject @com.google.inject.name.Named(Constants.FILE_EXTENSIONS) public String fileExtension; @Inject @Singleton private IndexedJvmTypeAccess indexedJvmTypeAccess; protected void installJvmTypeProvider(ResourceSet resourceSet) { Iterable<? extends String> classPathEntries = javaCompiler.getClasspathPathEntries(); classPathEntries = filter(classPathEntries, new Predicate<String>() { public boolean apply(String input) { return !Strings.isEmpty(input.trim()); } }); Iterable<URL> classPathUrls = Iterables.transform(classPathEntries, new Function<String, URL>() { public URL apply(String from) { try { return new File(from).toURI().toURL(); } catch (MalformedURLException e) { throw new RuntimeException(e); } } }); URLClassLoader urlClassLoader = new URLClassLoader(toArray(classPathUrls, URL.class), getClass().getClassLoader()); new ClasspathTypeProvider(urlClassLoader, resourceSet, indexedJvmTypeAccess); ((XtextResourceSet) resourceSet).setClasspathURIContext(urlClassLoader); } public BehaviorExecutor() { initCompiler(); try { tempFolder.create(); } catch (IOException e) { throw new RuntimeException(e); } } protected void initCompiler() { javaCompiler.addClassPathOfClass(getClass()); javaCompiler.addClassPathOfClass(StringExtensions.class); javaCompiler.addClassPathOfClass(Notifier.class); javaCompiler.addClassPathOfClass(org.eclipse.xtend.lib.annotations.Data.class); javaCompiler.addClassPathOfClass(EcorePackage.class); javaCompiler.addClassPathOfClass(XbasePackage.class); javaCompiler.addClassPathOfClass(XtendPackage.class); javaCompiler.addClassPathOfClass(Inject.class); javaCompiler.addClassPathOfClass(Properties1.class); javaCompiler.addClassPathOfClass(Function.class); javaCompiler.addClassPathOfClass(StringConcatenation.class); javaCompiler.addClassPathOfClass(Named.class); javaCompiler.addClassPathOfClass(ResultMatchers.class); javaCompiler.addClassPathOfClass(Matcher.class); javaCompiler.addClassPathOfClass(Matchers.class); javaCompiler.addClassPathOfClass(Test.class); javaCompiler.addClassPathOfClass(FeaturePackage.class); javaCompiler.addClassPathOfClass(SpecPackage.class); javaCompiler.addClassPathOfClass(BehaviorExecutor.class); javaCompiler.addClassPathOfClass(JvmOperation.class); javaCompiler.addClassPathOfClass(RegisteredValidatorTester.class); javaCompiler.addClassPathOfClass(QualifiedName.class); javaCompiler.addClassPathOfClass(Assertion.class); javaCompiler.addClassPathOfClass(org.hamcrest.Matchers.class); javaCompiler.addClassPathOfClass(Type.class); javaCompiler.addClassPathOfClass(Token.class); javaCompiler.addClassPathOfClass(Strings.class); javaCompiler.addClassPathOfClass(Suite.class); javaCompiler.addClassPathOfClass(Mockito.class); javaCompiler.addClassPathOfClass(Maps.class); javaCompiler.addClassPathOfClass(org.jnario.feature.tests.integration.Calculator.class); } public Result run(EObject object) { try { configureOutlet(); if(validate){ validate(object); } generateJava(object); return runExamples(object); } catch (Exception e) { e.printStackTrace(); try{ validate(object); }catch (AssertionError ex) { System.err.println(ex.getMessage()); } Exceptions.sneakyThrow(e); return null; } finally { tempFolder.delete(); } } protected void validate(EObject object) { Iterable<Issue> issues = validator.validate(object.eResource(), CheckMode.NORMAL_AND_FAST, CancelIndicator.NullImpl); Iterable<Issue> onlyErrors = filterErrors(issues); assertFalse("Validation errors\n" + Joiner.on("\n ").join(issues), onlyErrors.iterator().hasNext()); } protected Iterable<Issue> filterErrors(Iterable<Issue> issues) { Iterable<Issue> onlyErrors = filter(issues, new Predicate<Issue>() { public boolean apply(Issue input) { return input.getSeverity() == Severity.ERROR; } }); return onlyErrors; } private void configureOutlet() throws IOException { fsa.setOutputPath(tempFolder.getRoot().getAbsolutePath()); } protected void generateJava(EObject object) { generator.doGenerate(object.eResource(), fsa); } protected abstract Result runExamples(EObject object) throws MalformedURLException, ClassNotFoundException; protected void compileJavaFile(String packageName, String className) { List<String> args = findGeneratedJavaFiles(packageName); javaCompiler.compile(args); } protected Result execute(Class<?> cls) { return JUnitCore.runClasses(cls); } protected Class<?> loadGeneratedClass(String packageName, String specClassName) throws MalformedURLException, ClassNotFoundException { URLClassLoader classLoader = URLClassLoader.newInstance( new URL[] { tempFolder.getRoot().toURI().toURL() }, getClass() .getClassLoader()); if(packageName != null){ specClassName = packageName + "." + specClassName; } return Class.forName(specClassName, true, classLoader); } private List<String> findGeneratedJavaFiles(String packageName) { File packageDir = resolvePackageDir(packageName); return allJavaFilesIn(packageDir); } protected File resolvePackageDir(String packageName) { StringBuilder sb = new StringBuilder(); sb.append(tempFolder.getRoot().getAbsolutePath()); sb.append(File.separator); if(packageName != null){ sb.append(packageName .replaceAll("\\.", File.separator + File.separator)); sb.append(File.separator); } File packageDir = new File(sb.toString()); return packageDir; } protected List<String> allJavaFilesIn(File packageDir) { File[] allJavaFiles = packageDir.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { return name.endsWith(".java"); } }); List<String> result = newArrayList(); for (int i = 0; i < allJavaFiles.length; i++) { File file = allJavaFiles[i]; try { result.add(file.getCanonicalPath()); } catch (IOException e) { throw new RuntimeException(e); } } return result; } protected Result runTestsInClass(String className, String packageName) throws MalformedURLException, ClassNotFoundException { compileJavaFile(packageName, className); Class<?> testClass = loadGeneratedClass(packageName, className); return execute(testClass); } }