| /* |
| |
| Derby - Class org.apache.derbyTesting.functionTests.tests.compatibility.VersionCombinationConfigurator |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to you 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 org.apache.derbyTesting.functionTests.tests.compatibility; |
| |
| import java.io.File; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.security.AccessController; |
| import java.security.CodeSource; |
| import java.security.PrivilegedAction; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import junit.extensions.TestSetup; |
| import org.apache.derby.tools.sysinfo; |
| import org.apache.derbyTesting.junit.BaseTestCase; |
| import org.apache.derbyTesting.junit.BaseTestSuite; |
| import org.apache.derbyTesting.junit.DerbyDistribution; |
| import org.apache.derbyTesting.junit.DerbyVersion; |
| import org.apache.derbyTesting.junit.JDBC; |
| import org.apache.derbyTesting.junit.TestConfiguration; |
| |
| /** |
| * Generates a set of client-server combinations to run the compatibility |
| * tests for. |
| * <p> |
| * Due to the requirement for running with a variety of Derby versions, the |
| * compatibility test suite is run as multiple processes. The test is |
| * controlled from the main process (the process in which the test/suite is |
| * started), and this process spawns additional processes for each server |
| * version and each client version. In some cases it also has to spawn |
| * additional processes to accomplish other tasks. |
| * <p> |
| * For development purposes the default MATS suite is sufficient for ongoing |
| * work. Eventually, and at least before cutting a new release, the full |
| * development suite should be run, since it will test the trunk against all |
| * previous releases. The other suites will test old releases against each |
| * other, and as such they are of less interest since the old releases don't |
| * change. Note however that these suites can be used to test releases on |
| * branches where this version of the compatibility test doesn't exist (just |
| * add the JARs to the release repository and configure includes or excludes |
| * to suite your needs). |
| * <p> |
| * <strong>NOTE 1</strong>: The set of combinations computed by this class |
| * depends on the number of old releases available on the local computer. If |
| * there are no old releases available a warning will be emitted, but the test |
| * won't fail (it will test trunk vs trunk). |
| * <p> |
| * <strong>NOTE 2</strong>: trunk is defined as a distribution, although it |
| * hasn't been released yet. The reason is simple: we always want to test trunk |
| * for incompatibilities against older versions. |
| */ |
| public class VersionCombinationConfigurator { |
| |
| private static final String EMB_DRIVER = |
| "org.apache.derby.jdbc.EmbeddedDriver"; |
| |
| /** Name of the configuration, only used for informational purposes. */ |
| private final String name; |
| /** Decides if combinations have to involve trunk (as server or client). */ |
| private final boolean limitToTrunk; |
| /** Decides if only the latest branch release is eligible for inclusion. */ |
| private final boolean newestFixpackOnly; |
| private List<DerbyVersion> toInclude = Collections.emptyList(); |
| private List<DerbyVersion> toExclude = Collections.emptyList(); |
| |
| /** |
| * Returns the default configuration intended to be run as part of |
| * <tt>suites.all</tt>, which is a kind of minimal acceptance test (MATS). |
| * <p> |
| * The default configuration is defined to be all combinations that have |
| * trunk as either the server or the client. |
| * |
| * @return A configurator generating the default set of tests. |
| */ |
| public static VersionCombinationConfigurator getInstanceDevMATS() { |
| return new VersionCombinationConfigurator( |
| "default/MATS configuration", true, true); |
| } |
| |
| /** |
| * Returns a configuration that will test trunk against all other available |
| * releases. |
| * |
| * @return A configurator generating the default set of tests. |
| */ |
| public static VersionCombinationConfigurator getInstanceDevFull() { |
| return new VersionCombinationConfigurator( |
| "full development configuration", true, false); |
| } |
| |
| /** |
| * Returns a configuration where the newest releases within each |
| * major-minor version are tested against each other. |
| * <p> |
| * Given releases designated <tt>M.m.f.p</tt> (i.e. 10.8.1.2), this |
| * configuration will include all major-minor releases with the highest |
| * <ff>f</ff>. |
| * |
| * @return A configurator generating a reasonably sized test set. |
| */ |
| public static VersionCombinationConfigurator getInstanceOld() { |
| return new VersionCombinationConfigurator( |
| "historical configuration", false, true); |
| } |
| |
| /** |
| * Returns a configuration where all versions found are tested against |
| * each other. |
| * |
| * @return A configurator generating the full set of tests. |
| */ |
| public static VersionCombinationConfigurator getInstanceOldFull() { |
| return new VersionCombinationConfigurator( |
| "full historical configuration", false, false); |
| } |
| |
| /** |
| * Creates a version combination configurator. |
| * |
| * @param name name of the configurator |
| * @param limitToTrunk if true, only add combinations including trunk |
| * @param newestFixpackOnly whether or not to only include the newest |
| * release within each pair of major-minor version. |
| */ |
| private VersionCombinationConfigurator(String name, |
| boolean limitToTrunk, |
| boolean newestFixpackOnly) { |
| this.name = name; |
| this.limitToTrunk = limitToTrunk; |
| this.newestFixpackOnly = newestFixpackOnly; |
| } |
| |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * Adds compatibility tests to the specified suite. |
| * <p> |
| * The following determines how many tests are added: |
| * <ul> <li>available distributions locally (release repository)</li> |
| * <li>list of includes and/or excludes (by default empty)</li> |
| * <li>the configurator's current settings</li> |
| * </ul> |
| * |
| * @param suite the suite to add the tests to |
| * @return Number of compatibility runs added. |
| */ |
| public int addTests(BaseTestSuite suite) { |
| int runsAdded = 0; |
| List<DerbyDistribution> dists = filterVersions(); |
| DerbyDistribution newestDist = dists.get(0); |
| String newestTestingCode = newestDist.getTestingClasspath(); |
| // Generate a list of all the combinations. |
| for (DerbyDistribution server : dists) { |
| DerbyVersion serverVersion = server.getVersion(); |
| |
| // Check if testing of this server version should be skipped. |
| if (skipServerVersion(serverVersion)) { |
| continue; |
| } |
| |
| BaseTestSuite clientSuites = new BaseTestSuite( |
| "Client runs against server " + serverVersion.toString()); |
| for (DerbyDistribution client : dists) { |
| if (limitToTrunk && !server.equals(newestDist) && |
| !client.equals(newestDist)) { |
| continue; |
| } |
| clientSuites.addTest( |
| new ClientCompatibilityRunControl( |
| client, newestTestingCode, serverVersion)); |
| runsAdded++; |
| } |
| TestSetup setup = new VersionedNetworkServerTestSetup( |
| clientSuites, server, newestTestingCode); |
| suite.addTest(setup); |
| } |
| return runsAdded; |
| } |
| |
| public void setIncludes(List<DerbyVersion> toInclude) { |
| if (toInclude != null) { |
| this.toInclude = toInclude; |
| } |
| } |
| |
| public void setExcludes(List<DerbyVersion> toExclude) { |
| if (toExclude != null) { |
| this.toExclude = toExclude; |
| } |
| } |
| |
| /** |
| * Check if a certain server version should be skipped due to bugs that |
| * prevent it from working in the current environment. |
| * |
| * @param version the server version to check |
| * @return {@code true} if the specified version should be skipped, or |
| * {@code false} otherwise |
| */ |
| private boolean skipServerVersion(DerbyVersion version) { |
| |
| // DERBY-6098: Skip testing of server versions less than 10.10 if |
| // the JVM doesn't support JNDI. Earlier versions of the server don't |
| // accept connections if JNDI is not present. |
| if (!JDBC.vmSupportsJNDI() && version.lessThan(DerbyVersion._10_10)) { |
| println("Server version " + version + " was skipped because " + |
| "it requires JNDI to run."); |
| return true; |
| } |
| |
| // Default: don't skip |
| return false; |
| } |
| |
| /** |
| * Filters Derby distributions available in the distribution repository. |
| * |
| * @return A list of available and accepted Derby distributions. |
| */ |
| private List<DerbyDistribution> filterVersions() { |
| DerbyDistribution[] dists = |
| TestConfiguration.getReleaseRepository().getDistributions(); |
| List<DerbyDistribution> qualifiedDists = |
| new ArrayList<DerbyDistribution>(); |
| for (DerbyDistribution dist: dists) { |
| // Handle includes and excludes. |
| DerbyVersion version = dist.getVersion(); |
| if (!toInclude.isEmpty() && !toInclude.contains(version)) { |
| println(version.toString() + " not in include list"); |
| continue; |
| } |
| if (!toExclude.isEmpty() && toExclude.contains(version)) { |
| println(version.toString() + " in exclude list"); |
| continue; |
| } |
| |
| qualifiedDists.add(dist); |
| } |
| // If there are no qualified old distributions at this point, sound the |
| // alarm as we're probably looking at a misconfiguration. |
| if (qualifiedDists.isEmpty()) { |
| alarm("No old releases found for current configuration/environment"); |
| } |
| |
| // Now add the version we are running off. |
| DerbyDistribution runningDist = getRunningDistribution(); |
| if (!qualifiedDists.contains(runningDist)) { |
| qualifiedDists.add(runningDist); |
| } |
| qualifiedDists = sortAndFilterVersions(qualifiedDists); |
| |
| println("--- " + qualifiedDists.size() + " distributions qualified"); |
| for (DerbyDistribution d : qualifiedDists) { |
| println(d.getVersion().toString()); |
| } |
| |
| return qualifiedDists; |
| } |
| |
| /** |
| * Returns the running distribution, which is typically trunk. |
| * |
| * @return Information about the running distribution. |
| * @throws IllegalStateException if parsing the version string fails, if |
| * required Derby classes are missing, or if trunk is run off the |
| * classes directory |
| */ |
| private DerbyDistribution getRunningDistribution() { |
| File libDir; |
| try { |
| libDir = getJarDirectoryOf(Class.forName(EMB_DRIVER)); |
| } catch (ClassNotFoundException cnfe) { |
| // Add relevant information to the error message, the cause and |
| // its stack trace is not printed by default in this context. |
| throw new IllegalStateException( |
| "missing Derby class: " + cnfe.getMessage(), cnfe); |
| } |
| File testingDir = getJarDirectoryOf(getClass()); |
| DerbyVersion version = DerbyVersion.parseVersionString( |
| sysinfo.getVersionString()); |
| DerbyDistribution dist = DerbyDistribution.newInstance( |
| version, libDir, testingDir); |
| if (dist == null) { |
| throw new IllegalStateException( |
| "failed to get running distribution (programming error?)"); |
| } |
| return dist; |
| } |
| |
| /** |
| * Returns the directory for the JAR file containing the given class. |
| * |
| * @return A directory path. |
| * @throws IllegalStateException if the class isn't loaded from a JAR |
| */ |
| private File getJarDirectoryOf(Class clazz) { |
| File jarPath = new File(getClassURI(clazz)); |
| if (jarPath.isDirectory()) { |
| throw new IllegalStateException("only running off jars is " + |
| "supported, currently running off directory " + jarPath); |
| } |
| // Get the directory the JAR file is living in. |
| return jarPath.getParentFile(); |
| } |
| |
| /** |
| * Sorts and filters out distributions based on the configurator settings. |
| * |
| * @param distributions list of distributions to filter |
| * @return A filtered list of distributions. |
| */ |
| private List<DerbyDistribution> sortAndFilterVersions( |
| List<DerbyDistribution> distributions) { |
| // Sort the releases based on the version number (highest first). |
| Collections.sort(distributions); |
| Collections.reverse(distributions); |
| |
| DerbyDistribution prev = null; |
| if (newestFixpackOnly) { |
| List<DerbyDistribution> filtered = |
| new ArrayList<DerbyDistribution>(); |
| for (DerbyDistribution d : distributions) { |
| DerbyVersion ver = d.getVersion(); |
| if (prev == null || prev.getVersion().greaterMinorThan(ver)) { |
| filtered.add(d); |
| } else { |
| println("ignored " + ver.toString() + |
| ", not the newest fixpack version for " + |
| ver.getMajor() + "." + ver.getMinor()); |
| } |
| prev = d; |
| } |
| distributions = filtered; |
| } |
| return distributions; |
| } |
| |
| /** |
| * Returns the URI of the source for the specified class. |
| * |
| * @param cl class to find the source for |
| * @return A {@code URI} pointing to the source, or {@code null} it cannot |
| * be obtained. |
| */ |
| static URI getClassURI(final Class cl) { |
| return AccessController.doPrivileged(new PrivilegedAction<URI>() { |
| public URI run() { |
| CodeSource cs = cl.getProtectionDomain().getCodeSource(); |
| if (cs != null) { |
| try { |
| return cs.getLocation().toURI(); |
| } catch (URISyntaxException use) { |
| // Shouldn't happen, fall through and return null. |
| BaseTestCase.alarm("bad URI: " + use.getMessage()); |
| } |
| } |
| return null; |
| } |
| }); |
| } |
| |
| // Forwarding convenience methods |
| |
| private static void println(String msg) { |
| BaseTestCase.println(msg); |
| } |
| |
| private static void alarm(String msg) { |
| BaseTestCase.alarm(msg); |
| } |
| } |