/**
* Copyright (C) 2009-2014 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.sql.test;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Map;
import java.util.TimeZone;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;
import org.joda.time.DateTimeZone;
public class YamlTestFinder
{
static {
String timezone = "UTC";
DateTimeZone.setDefault(DateTimeZone.forID(timezone));
// We still have usages of java.util.Date, but thanks to the ConfigurationServiceImpl.validateTimezone
// These should always be the same in production, so leaving this here until we cleanup remaining usages
TimeZone.setDefault(TimeZone.getTimeZone(timezone));
}
private static final String PROPERTY_PREFIX = "com.foundationdb.sql.test.yaml";
/** The directory containing the YAML files. */
private static final String RESOURCE_DIR_PROPERTY = PROPERTY_PREFIX + ".RESOURCE_DIR";
/** Whether to search the resource directory recursively for test files. */
private static final String RECURSIVE_PROPERTY = PROPERTY_PREFIX + ".RECURSIVE";
/** A resource known to be in the root to look for and find where the rest are. */
private static final String RESOURCE_MARKER = "/com/foundationdb/sql/test/yaml/README";
/**
* A regular expression matching the names of the YAML files in
* the resource directories.
*/
private static final String FILE_NAME_REGEXP = "test-.*[.]yaml";
private final Collection<Object[]> params = new ArrayList<>();
private final Pattern filenamePattern = Pattern.compile(FILE_NAME_REGEXP);
private final String baseName;
private YamlTestFinder(URL baseURL) {
this.baseName = baseURL.toString();
}
private void addURL(URL url) {
// URI.relativize() ought to do this, but is confused about jar:file: URLs.
String name = url.toString();
int idx;
if (name.startsWith(baseName)) {
idx = baseName.length();
}
else {
idx = name.lastIndexOf('/');
if (idx < 0) {
idx = 0;
}
else {
idx++;
}
}
params.add(new Object[] {
name.substring(idx, name.length() - 5),
url
});
}
/** Return a collection of class instantiation parameters for YAML tests.
* The parameters are: <code>String caseName, URL url</code>.
*/
public static Iterable<Object[]> findTests() {
YamlTestFinder finder;
String resourceDirName = System.getProperty(RESOURCE_DIR_PROPERTY);
if (resourceDirName != null) {
// User-specified file location.
File resourceDir = new File(resourceDirName);
boolean recursive = Boolean.valueOf(System.getProperty(RECURSIVE_PROPERTY,
"true"));
try {
finder = new YamlTestFinder(resourceDir.toURI().toURL());
}
catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
finder.collectFiles(resourceDir, recursive);
}
else {
URL url = YamlTestFinder.class.getResource(RESOURCE_MARKER);
if (url == null) {
throw new RuntimeException("Problem finding tests: " + RESOURCE_MARKER);
}
try {
finder = new YamlTestFinder(new URL(url, "."));
}
catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
if ("file".equals(url.getProtocol())) {
// Maven-specified file location.
finder.collectFiles(new File(url.getPath()).getParentFile(), true);
}
else {
// Inside test.jar.
finder.collectResources(url);
}
}
return finder.params;
}
protected boolean match(String filename) {
return filenamePattern.matcher(filename).matches();
}
/**
* Add files from the directory that match the pattern to params, recursing
* if appropriate.
*/
private void collectFiles(File directory, final boolean recursive) {
File[] files = directory.listFiles(
new FileFilter() {
@Override
public boolean accept(File file) {
if (file.isDirectory()) {
if (recursive) {
collectFiles(file, recursive);
}
}
else {
if (match(file.getName())) {
try {
addURL(file.toURI().toURL());
}
catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
}
}
return false;
}
}
);
if (files == null) {
throw new RuntimeException("Problem accessing directory: " + directory);
}
}
// In normal operation, where mvn test is run from the sql-layer
// root, Maven's reactor will substitute access to files in the
// sibling module, which is much nicer for iterative development.
// This is for the case where the test-jar is actually being used,
// such as when mvn test is run in the fdb-sql-layer-core child.
private void collectResources(URL url) {
String fullURL = url.toString();
int bang = fullURL.indexOf('!');
if (!fullURL.startsWith("jar:file:") ||
(bang < 0)) {
throw new RuntimeException("Unexpected resource location: " + fullURL);
}
String jarFilename = fullURL.substring(9, bang);
try {
JarFile jarFile = new JarFile(jarFilename);
Enumeration<JarEntry> e = jarFile.entries();
while (e.hasMoreElements()) {
JarEntry jarEntry = e.nextElement();
if (jarEntry.isDirectory()) continue;
String filename = jarEntry.getName();
int idx = filename.lastIndexOf('/');
String name = (idx < 0) ? filename : filename.substring(idx+1);
if (match(name)) {
addURL(new URL("jar:file:" + jarFilename + "!/" + filename));
}
}
}
catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
catch (IOException ex) {
throw new RuntimeException("Error reading from " + jarFilename, ex);
}
}
}