Java tutorial
/* * Copyright (C) 2016 Keith M. Hughes * Copyright (C) 2013 Google 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 io.smartspaces.workbench.project.test; import io.smartspaces.SimpleSmartSpacesException; import io.smartspaces.SmartSpacesException; import io.smartspaces.util.io.FileSupport; import io.smartspaces.util.io.FileSupportImpl; import io.smartspaces.workbench.language.ProgrammingLanguageCompiler; import io.smartspaces.workbench.language.ProgrammingLanguageSupport; import io.smartspaces.workbench.project.Project; import io.smartspaces.workbench.project.ProjectFileLayout; import io.smartspaces.workbench.project.ProjectTaskContext; import io.smartspaces.workbench.project.java.JvmProjectExtension; import io.smartspaces.workbench.project.java.JvmProjectSupport; import io.smartspaces.workbench.project.java.StandardJvmProjectSupport; import org.apache.commons.logging.Log; import java.io.File; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; /** * A test runner which finds a bunch of classes which are JUnit test classes and * runs them. * * <p> * Tests are run in a test runner which is isolated from the classloader which * loads the Smart Spaces workbench. This is to prevent mixing jar files which * are found in both the Smart Spaces Workbench and controller. * * @author Keith M. Hughes */ public class IsolatedClassloaderJavaTestRunner implements JavaTestRunner { /** * The classname for the isolated test runner. */ public static final String ISOLATED_TESTRUNNER_CLASSNAME = "io.smartspaces.workbench.project.test.IsolatedJavaTestRunner"; /** * The method name for running tests on the isolated test runner. */ public static final String ISOLATED_TESTRUNNER_METHODNAME = "runTests"; /** * The project support to use. */ private final JvmProjectSupport projectSupport = new StandardJvmProjectSupport(); /** * File support for class. */ private final FileSupport fileSupport = FileSupportImpl.INSTANCE; @Override public void runTests(File jarDestinationFile, JvmProjectExtension extension, ProjectTaskContext context, ProgrammingLanguageSupport languageSupport) throws SmartSpacesException { List<File> compilationFiles = new ArrayList<>(); Project project = context.getProject(); languageSupport.getCompilationFiles( fileSupport.newFile(project.getBaseDirectory(), languageSupport.getTestSourceDirectory()), compilationFiles); languageSupport.getCompilationFiles( fileSupport.newFile(context.getBuildDirectory(), languageSupport.getTestGeneratedSourceDirectory()), compilationFiles); if (compilationFiles.isEmpty()) { // No tests mean they all succeeded in some weird philosophical sense. return; } context.getLog().info(String.format("Running tests for project %s", context.getProject().getBaseDirectory().getAbsolutePath())); List<File> classpath = getClasspath(context, extension, jarDestinationFile); File buildFolder = fileSupport.newFile(context.getBuildDirectory(), ProjectFileLayout.BUILD_DIRECTORY_CLASSES_TESTS); fileSupport.directoryExists(buildFolder); List<String> compilerOptions = languageSupport.getCompilerOptions(context); ProgrammingLanguageCompiler compiler = languageSupport.newCompiler(); compiler.compile(context, buildFolder, classpath, compilationFiles, compilerOptions); runJavaUnitTests(buildFolder, jarDestinationFile, extension, context); } /** * Detect and run any JUnit test classes. * * @param testCompilationFolder * folder where the test classes were compiled * @param jarDestinationFile * the jar that was built * @param extension * the Java project extension for the project (can be {@code null}) * @param context * the build context * * @throws SmartSpacesException * the tests failed */ private void runJavaUnitTests(File testCompilationFolder, File jarDestinationFile, JvmProjectExtension extension, ProjectTaskContext context) throws SmartSpacesException { List<File> classpath = getClasspath(context, extension, jarDestinationFile, testCompilationFolder); List<URL> urls = new ArrayList<>(); for (File classpathElement : classpath) { try { urls.add(classpathElement.toURL()); } catch (MalformedURLException e) { context.getWorkbenchTaskContext().getWorkbench().getLog().error(String.format( "Error while adding %s to the unit test classpath", classpathElement.getAbsolutePath()), e); } } URLClassLoader classLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]), context.getWorkbenchTaskContext().getWorkbench().getBaseClassLoader()); runTestsInIsolation(testCompilationFolder, classLoader, context); } /** * Build a class path structure based on the given jar destination file and * project. * * @param context * the project task context * @param extension * the java project extension * @param additionalFiles * any number of additional Files to add to the class path * @return a List<File> of all required class path entries */ private List<File> getClasspath(ProjectTaskContext context, JvmProjectExtension extension, File... additionalFiles) { List<File> classpath = new ArrayList<>(); for (File additionalFile : additionalFiles) { classpath.add(additionalFile); } classpath.add(fileSupport.newFile(context.getProject().getBaseDirectory(), ProjectFileLayout.SOURCE_TEST_RESOURCES)); projectSupport.getProjectClasspath(true, context, classpath, extension, context.getWorkbenchTaskContext()); return classpath; } /** * Run the given tests in the given class loader. This method is somewhat * complicated, since it needs to use reflsection to isolate the test runner * in a separate class loader that does not derive from the current class. * * @param testCompilationFolder * the folder containing the test classes * @param classLoader * classLoader to use for running tests * @param context * the build context * * @throws SmartSpacesException * the tests failed */ private void runTestsInIsolation(File testCompilationFolder, URLClassLoader classLoader, ProjectTaskContext context) throws SmartSpacesException { boolean result = false; try { // This code is equivalent to TestRunnerBridge.runTests(testClassNames, // classLoader), except // that it is sanitized through the test class loader. Class<?> testRunnerClass = classLoader.loadClass(ISOLATED_TESTRUNNER_CLASSNAME); Method runner = testRunnerClass.getMethod(ISOLATED_TESTRUNNER_METHODNAME, File.class, URLClassLoader.class, Log.class); Object testRunner = testRunnerClass.newInstance(); result = (Boolean) runner.invoke(testRunner, testCompilationFolder, classLoader, context.getWorkbenchTaskContext().getWorkbench().getLog()); } catch (Exception e) { // This catch here for the reflection errors context.getWorkbenchTaskContext().getWorkbench().getLog().error("Error running tests", e); throw new SmartSpacesException("Error running tests", e); } if (!result) { throw new SimpleSmartSpacesException("Unit tests failed"); } } }