File: VersionCombinationConfigurator.java

package info (click to toggle)
derby 10.14.2.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye
  • size: 78,896 kB
  • sloc: java: 691,930; sql: 42,686; xml: 20,511; sh: 3,373; sed: 96; makefile: 60
file content (392 lines) | stat: -rw-r--r-- 15,624 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
/*

   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);
    }
}