Java tutorial
/* * ShiningPanda plug-in for Jenkins * Copyright (C) 2011-2015 ShiningPanda S.A.S. * * This program is free software: you can redistribute it and/or modify * it under the terms of its license which incorporates the terms and * conditions of version 3 of the GNU Affero General Public License, * supplemented by the additional permissions under the GNU Affero GPL * version 3 section 7: if you modify this program, or any covered work, * by linking or combining it with other code, such other code is not * for that reason alone subject to any of the requirements of the GNU * Affero GPL version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * license for more details. * * You should have received a copy of the license along with this program. * If not, see <https://raw.github.com/jenkinsci/shiningpanda-plugin/master/LICENSE.txt>. */ package jenkins.plugins.shiningpanda; import java.beans.PropertyDescriptor; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.util.Properties; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.io.filefilter.IOFileFilter; import org.apache.commons.lang.StringUtils; import org.jvnet.hudson.test.HudsonTestCase; import hudson.FilePath; import hudson.matrix.AxisList; import hudson.matrix.MatrixProject; import hudson.model.AbstractProject; import hudson.model.Item; import hudson.tasks.Builder; import jenkins.plugins.shiningpanda.matrix.PythonAxis; import jenkins.plugins.shiningpanda.matrix.ToxAxis; import jenkins.plugins.shiningpanda.tools.PythonInstallation; import jenkins.plugins.shiningpanda.workspace.MasterWorkspace; import jenkins.plugins.shiningpanda.workspace.SlaveWorkspace; import jenkins.plugins.shiningpanda.workspace.Workspace; public abstract class ShiningPandaTestCase extends HudsonTestCase { /** * Key for CPython 2.x's home in test.properties file. */ private final static String CPYTHON_2_HOME_KEY = "CPython.2.Home"; /** * Key for CPython 3.x's home in test.properties file. */ private final static String CPYTHON_3_HOME_KEY = "CPython.3.Home"; /** * Key for PyPy's home in test.properties file. */ private final static String PYPY_HOME_KEY = "PyPy.Home"; /** * Key for Jython's home in test.properties file. */ private final static String JYTHON_HOME_KEY = "Jython.Home"; /** * Name of CPython 2.x. */ private final static String CPYTHON_2_NAME = "CPython-2"; /** * Name of CPython 3.x. */ private final static String CPYTHON_3_NAME = "CPython-3"; /** * Name of PyPy. */ private final static String PYPY_NAME = "PyPy"; /** * Name of JYTHON. */ private final static String JYTHON_NAME = "Jython"; /** * Load the test properties. First load the test.properties.model file, and * then test.properties if exists. * * @return The test properties. * @throws IOException */ protected Properties getTestProperties() throws IOException { Properties properties = new Properties(); properties.load(getClass().getResourceAsStream("/test.properties.model")); InputStream stream = getClass().getResourceAsStream("/test.properties"); if (stream != null) properties.load(stream); return properties; } /** * Get the value for the given test properties key. * * @param key * The key. * @return The value. * @throws IOException */ protected String getTestProperty(String key) throws IOException { String value = System.getProperty(key); if (value == null) value = getTestProperties().getProperty(key); if (value == null) throw new IOException("failed to find a value for " + key); return value; } /** * Get the CPython 2.x's home. * * @return The home folder. * @throws IOException */ protected String getCPython2Home() throws IOException { return getTestProperty(CPYTHON_2_HOME_KEY); } /** * Get the PyPy's home. * * @return The home folder. * @throws IOException */ protected String getPyPyHome() throws IOException { return getTestProperty(PYPY_HOME_KEY); } /** * Get the Jython's home. * * @return The home folder. * @throws IOException */ protected String getJythonHome() throws IOException { return getTestProperty(JYTHON_HOME_KEY); } /** * Get the CPython 3.x's home * * @return The home folder. * @throws IOException */ protected String getCPython3Home() throws IOException { return getTestProperty(CPYTHON_3_HOME_KEY); } /** * Delete a VIRTUALENV. * * @param home * The home folder of the VIRTUALENV. * @throws IOException */ protected void deleteVirtualenv(File home) throws IOException { // Check if exists if (!home.exists()) return; // Do not follow symbolic links IOFileFilter filter = new IOFileFilter() { /* * (non-Javadoc) * * @see * org.apache.commons.io.filefilter.IOFileFilter#accept(java.io. * File, java.lang.String) */ public boolean accept(File dir, String name) { return accept(dir); } /* * (non-Javadoc) * * @see * org.apache.commons.io.filefilter.IOFileFilter#accept(java.io. * File) */ public boolean accept(File file) { try { return !FileUtils.isSymlink(file); } catch (IOException e) { e.printStackTrace(); } return false; } }; // Go threw the selected files to set write permission for (File file : FileUtils.listFiles(home, filter, filter)) { // Set write permission file.setWritable(true); } // Delete the directory FileUtils.deleteDirectory(home); } /** * Create a VIRTUALENV. * * @param home * The home of this VIRTUALENV. * @return The home of the VIRTUALENV * @throws Exception */ protected File createVirtualenv(File home) throws Exception { // Clean deleteVirtualenv(home); // Create a process to create the VIRTUALENV ProcessBuilder pb = new ProcessBuilder("virtualenv", home.getAbsolutePath()); // Start the process Process process = pb.start(); // Check exit code assertEquals(0, process.waitFor()); // Return the home folder return home; } /** * Create a fake PYTHON installation with spaces in its home folder path. * * @return The home folder * @throws IOException */ protected File createFakePythonInstallationWithWhitespaces() throws IOException { // Create a home folder with spaces in its name File home = createTmpDir("bad move"); // Cleanup if already exists FileUtils.deleteDirectory(home); // Get the binary folder File bin = new File(home, "bin"); // Create it bin.mkdir(); // Get the PYTHON binary path File binary = new File(bin, "python"); // Create the file FileUtils.writeStringToFile(binary, "fake installation"); // Return home folder return home; } /** * Configure a PYTHON installation. * * @param name * The name of the installation. * @param home * The home folder for this installation. * @return */ protected PythonInstallation configurePython(String name, String home) { PythonInstallation[] installations = getPythonInstallations(); PythonInstallation[] newIntallations = new PythonInstallation[installations.length + 1]; int index = 0; for (PythonInstallation installation : installations) { newIntallations[index] = installation; index++; } PythonInstallation newInstallation = new PythonInstallation(name, home, NO_PROPERTIES); newIntallations[index] = newInstallation; getPythonInstallationDescriptor().setInstallations(newIntallations); return newInstallation; } /** * Configure a CPython 2.x installation. * * @return The installation. * @throws Exception */ protected PythonInstallation configureCPython2() throws Exception { return configurePython(CPYTHON_2_NAME, getCPython2Home()); } /** * Configure a CPython 3.x installation. * * @return The installation. * @throws Exception */ protected PythonInstallation configureCPython3() throws Exception { return configurePython(CPYTHON_3_NAME, getCPython3Home()); } /** * Configure a PyPy installation. * * @return The installation. * @throws Exception */ protected PythonInstallation configurePyPy() throws Exception { return configurePython(PYPY_NAME, getPyPyHome()); } /** * Configure a JYTHON installation. * * @return The installation. * @throws Exception */ protected PythonInstallation configureJython() throws Exception { return configurePython(JYTHON_NAME, getJythonHome()); } /** * Configure all Python installations. * * @return List of Python installations. * @throws Exception */ protected PythonInstallation[] configureAllPythons() throws Exception { configureCPython2(); configureCPython3(); configurePyPy(); return getPythonInstallations(); } /** * Get the Python's installations descriptor. * * @return The descriptor. */ protected PythonInstallation.DescriptorImpl getPythonInstallationDescriptor() { return hudson.getDescriptorByType(PythonInstallation.DescriptorImpl.class); } /** * Get the list of Python's installations. * * @return The list of installations. */ protected PythonInstallation[] getPythonInstallations() { return getPythonInstallationDescriptor().getInstallations(); } /** * Performs a configuration round-trip testing for a builder on free-style * project. * * @param before * The builder. * @return The reloaded builder. * @throws Exception */ protected <B extends Builder> B configFreeStyleRoundtrip(B before) throws Exception { return configRoundtrip(before); } /** * Performs a configuration round-trip testing for a builder on a matrix * project with a Python axis. * * @param before * The builder. * @return The reloaded builder. * @throws Exception */ @SuppressWarnings("unchecked") protected <B extends Builder> B configPythonMatrixRoundtrip(B before) throws Exception { configureAllPythons(); MatrixProject p = createMatrixProject(); p.setAxes(new AxisList( new PythonAxis(new String[] { CPYTHON_2_NAME, CPYTHON_3_NAME, PYPY_NAME, JYTHON_NAME }))); p.getBuildersList().add(before); configRoundtrip((Item) p); return (B) p.getBuildersList().get(before.getClass()); } /** * Performs a configuration round-trip testing for a builder on a matrix * project with a Tox axis. * * @param before * The builder. * @return The reloaded builder. * @throws Exception */ @SuppressWarnings("unchecked") protected <B extends Builder> B configToxMatrixRoundtrip(B before) throws Exception { configureAllPythons(); MatrixProject p = createMatrixProject(); p.setAxes(new AxisList(new ToxAxis(new String[] { "py27", "py32", "pypy", "jython" }))); p.getBuildersList().add(before); configRoundtrip((Item) p); return (B) p.getBuildersList().get(before.getClass()); } /** * Search a field in the provided class and its super classes. * * @param klass * The class to search in. * @param p * The field to search. * @return The field, or null if no such field. */ protected Field getField(@SuppressWarnings("rawtypes") Class klass, String p) { while (klass != Object.class) { try { return klass.getDeclaredField(p); } catch (NoSuchFieldException e) { } klass = klass.getSuperclass(); } return null; } /** * Same as assertEqualBeans, but works on protected and private fields. * * @param lhs * The initial object. * @param rhs * The final object. * @param properties * The properties to check. * @throws Exception */ public void assertEqualBeans2(Object lhs, Object rhs, String properties) throws Exception { assertNotNull("lhs is null", lhs); assertNotNull("rhs is null", rhs); for (String p : properties.split(",")) { PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor(lhs, p); Object lp, rp; if (pd == null) { Field f = getField(lhs.getClass(), p); assertNotNull("No such property " + p + " on " + lhs.getClass(), f); boolean accessible = f.isAccessible(); if (!accessible) f.setAccessible(true); lp = f.get(lhs); rp = f.get(rhs); f.setAccessible(accessible); } else { lp = PropertyUtils.getProperty(lhs, p); rp = PropertyUtils.getProperty(rhs, p); } if (lp != null && rp != null && lp.getClass().isArray() && rp.getClass().isArray()) { // deep array equality comparison int m = Array.getLength(lp); int n = Array.getLength(rp); assertEquals("Array length is different for property " + p, m, n); for (int i = 0; i < m; i++) assertEquals(p + "[" + i + "] is different", Array.get(lp, i), Array.get(rp, i)); return; } assertEquals("Property " + p + " is different", lp, rp); } } /** * Get the JENKINS configuration file. * * @return The configuration file. */ public File getConfigFile() { return new File(jenkins.getRootDir(), "config.xml"); } /** * Get the configuration file for this project. * * @param project * The project * @return The configuration file */ public File getConfigFile(AbstractProject<?, ?> project) { return new File(jenkins.getRootDir(), StringUtils.join(new String[] { "jobs", project.getName(), "config.xml" }, File.separator)); } /** * Create a workspace. * * @return The workspace * @throws IOException */ public Workspace getWorkspace() throws IOException { return Workspace.fromHome(new FilePath(createTmpDir())); } /** * Create a master workspace. * * @return The workspace * @throws IOException */ public MasterWorkspace getMasterWorkspace() throws IOException { return new MasterWorkspace(new FilePath(createTmpDir())); } /** * Create a slave workspace. * * @return The workspace * @throws IOException */ public SlaveWorkspace getSlaveWorkspace() throws IOException { return new SlaveWorkspace(new FilePath(createTmpDir())); } /** * Get the packages directory. * * @return The package directory */ public File getPackagesDir() { return new File(jenkins.getRootDir(), Workspace.BASENAME + File.separator + Workspace.PACKAGES); } /** * Create the packages directory. * * @return The packages directory */ public File createPackagesDir() { File packagesDir = getPackagesDir(); packagesDir.mkdirs(); return packagesDir; } public File createTmpDir(String... parts) throws IOException { File file = new File(createTmpDir(), StringUtils.join(parts, File.separator)); file.mkdirs(); return file; } /** * Convert a FilePath to a File. * * @param filePath * The FilePath to convert * @return The resulting file */ public File toFile(FilePath filePath) { return new File(filePath.getRemote()); } /** * Assert that file exists * * @param file * The file to check */ public void assertFile(File file) { assertTrue("file does not exist: " + file.getAbsolutePath(), file.isFile()); } /** * Assert that file exists * * @param filePath * The file to check */ public void assertFile(FilePath filePath) { assertFile(toFile(filePath)); } /** * Assert that directory exists * * @param file * The directory to check */ public void assertDirectory(File file) { assertTrue("directory does not exist: " + file.getAbsolutePath(), file.isDirectory()); } /** * Assert that directory exists * * @param filePath * The directory to check */ public void assertDirectory(FilePath filePath) { assertDirectory(toFile(filePath)); } /** * Assert that file does not exist * * @param file * The file to check */ public void assertNotExists(File file) { assertFalse("file exists: " + file.getAbsolutePath(), file.exists()); } /** * Assert that file does not exist * * @param filePath * The file to check */ public void assertNotExists(FilePath filePath) { assertNotExists(toFile(filePath)); } /** * Check that files contains the same thing. * * @param file1 * The first file * @param file2 * The second file */ public void assertContentEquals(File file1, File file2) { assertFile(file1); assertFile(file2); try { assertEquals("file content differ: " + file1.getAbsolutePath() + " != " + file2.getAbsolutePath(), FileUtils.readFileToString(file1), FileUtils.readFileToString(file2)); } catch (IOException e) { fail("failed to read file content: " + e.getMessage()); } } /** * Check that files contains the same thing. * * @param file1 * The first file * @param file2 * The second file */ public void assertContentEquals(FilePath filePath1, FilePath filePath2) { assertContentEquals(toFile(filePath1), toFile(filePath2)); } /** * Get a FilePath given a path as string * * @param pathname * The path * @return The file object */ public FilePath getFilePath(String pathname) { return new FilePath(new File(pathname)); } }