/*
*
* Copyright 2013 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.netflix.nicobar.groovy2.compile;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.assertNotNull;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections.CollectionUtils;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.customizers.CompilationCustomizer;
import org.codehaus.groovy.tools.GroovyClass;
import org.testng.annotations.Test;
import com.netflix.nicobar.core.archive.PathScriptArchive;
import com.netflix.nicobar.groovy2.internal.compile.Groovy2CompilerHelper;
import com.netflix.nicobar.groovy2.testutil.GroovyTestResourceUtil;
import com.netflix.nicobar.groovy2.testutil.GroovyTestResourceUtil.TestScript;
/**
* Unit Tests for {@link Groovy2CompilerHelper}
*
* @author James Kojo
* @author Vasanth Asokan
*/
public class Groovy2CompilerHelperTest {
/**
* Compile using current classloader, and no dependencies
* @throws Exception
*/
@Test
public void testSimpleCompile() throws Exception {
Path scriptRootPath = GroovyTestResourceUtil.findRootPathForScript(TestScript.HELLO_WORLD);
PathScriptArchive scriptArchive = new PathScriptArchive.Builder(scriptRootPath)
.setRecurseRoot(false)
.addFile(TestScript.HELLO_WORLD.getScriptPath())
.build();
Set<GroovyClass> compiledClasses = new Groovy2CompilerHelper(Files.createTempDirectory("Groovy2CompilerHelperTest"))
.addScriptArchive(scriptArchive)
.compile();
assertFalse(CollectionUtils.isEmpty(compiledClasses));
TestByteLoadingClassLoader testClassLoader = new TestByteLoadingClassLoader(getClass().getClassLoader(), compiledClasses);
Class<?> loadedClass = testClassLoader.loadClass(TestScript.HELLO_WORLD.getClassName());
assertNotNull(loadedClass);
Object instance = loadedClass.newInstance();
Method method = loadedClass.getMethod("getMessage");
String message = (String)method.invoke(instance);
assertEquals(message, "Hello, World!");
}
/**
* Compile with custom config
* @throws Exception
*/
@Test
public void testCompileWithCompilerCustomizer() throws Exception {
Path scriptRootPath = GroovyTestResourceUtil.findRootPathForScript(TestScript.HELLO_WORLD);
PathScriptArchive scriptArchive = new PathScriptArchive.Builder(scriptRootPath)
.setRecurseRoot(false)
.addFile(TestScript.HELLO_WORLD.getScriptPath())
.build();
TestCompilationCustomizer customizer = new TestCompilationCustomizer();
CompilerConfiguration compilerConfig = new CompilerConfiguration();
compilerConfig.addCompilationCustomizers(customizer);
Set<GroovyClass> compiledClasses = new Groovy2CompilerHelper(Files.createTempDirectory("Groovy2CompilerHelperTest"))
.addScriptArchive(scriptArchive)
.withConfiguration(compilerConfig)
.compile();
assertTrue(customizer.isExecuted(), "customizer has not been executed");
assertFalse(CollectionUtils.isEmpty(compiledClasses));
TestByteLoadingClassLoader testClassLoader = new TestByteLoadingClassLoader(getClass().getClassLoader(), compiledClasses);
Class<?> loadedClass = testClassLoader.loadClass(TestScript.HELLO_WORLD.getClassName());
assertNotNull(loadedClass);
Object instance = loadedClass.newInstance();
Method method = loadedClass.getMethod("getMessage");
String message = (String)method.invoke(instance);
assertEquals(message, "Hello, World!");
}
/**
* Compile a script with a package name using current classloader, and no dependencies
*/
@Test
public void testCompileWithPackage() throws Exception {
Path scriptRootPath = GroovyTestResourceUtil.findRootPathForScript(TestScript.HELLO_PACKAGE);
PathScriptArchive scriptArchive = new PathScriptArchive.Builder(scriptRootPath)
.setRecurseRoot(false)
.addFile(TestScript.HELLO_PACKAGE.getScriptPath())
.build();
Set<GroovyClass> compiledClasses = new Groovy2CompilerHelper(Files.createTempDirectory("Groovy2CompilerHelperTest"))
.addScriptArchive(scriptArchive)
.compile();
assertFalse(CollectionUtils.isEmpty(compiledClasses));
TestByteLoadingClassLoader testClassLoader = new TestByteLoadingClassLoader(getClass().getClassLoader(), compiledClasses);
Class<?> loadedClass = testClassLoader.loadClass(TestScript.HELLO_PACKAGE.getClassName());
assertNotNull(loadedClass);
Object instance = loadedClass.newInstance();
Method method = loadedClass.getMethod("getMessage");
String message = (String)method.invoke(instance);
assertEquals(message, "Hello, Package!");
}
/**
* Test class loader that can load bytes provided by the groovy compiler
*/
public static class TestByteLoadingClassLoader extends ClassLoader {
private final Map<String, byte[]> classBytes;
public TestByteLoadingClassLoader(ClassLoader parentClassLoader, Set<GroovyClass> groovyClasses) {
super(parentClassLoader);
this.classBytes = new HashMap<String, byte[]>();
for (GroovyClass groovyClass : groovyClasses) {
classBytes.put(groovyClass.getName(), groovyClass.getBytes());
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = classBytes.get(name);
if (bytes == null) {
throw new ClassNotFoundException("Couldn't find " + name);
}
return defineClass(name, bytes, 0, bytes.length);
}
}
/**
* Test compilation customizer
*/
private static class TestCompilationCustomizer extends CompilationCustomizer {
private boolean executed = false;
public TestCompilationCustomizer() {
super(CompilePhase.SEMANTIC_ANALYSIS);
}
public boolean isExecuted() {
return this.executed;
}
@Override
public void call(SourceUnit source, GeneratorContext context,
ClassNode classNode) throws CompilationFailedException {
this.executed = true;
};
}
}