/*
* #%L
* OME Bio-Formats manual and automated test suite.
* %%
* Copyright (C) 2006 - 2015 Open Microscopy Environment:
* - Board of Regents of the University of Wisconsin-Madison
* - Glencoe Software, Inc.
* - University of Dundee
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 2 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-2.0.html>.
* #L%
*/
package loci.tests.testng;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.FieldPosition;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import loci.common.ByteArrayHandle;
import loci.common.Constants;
import loci.common.DataTools;
import loci.common.DateTools;
import loci.common.Location;
import loci.common.RandomAccessInputStream;
import loci.formats.IFormatReader;
import loci.formats.IFormatWriter;
import loci.formats.ImageReader;
import loci.formats.in.SlideBook6Reader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility methods for use with TestNG tests.
*
*/
public class TestTools {
// -- Constants --
private static final Logger LOGGER = LoggerFactory.getLogger(TestTools.class);
public static final String DIVIDER =
"----------------------------------------";
public static final String baseConfigName = ".bioformats";
/** Calculate the SHA-1 of a byte array. */
public static String sha1(byte[] b, int offset, int len) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.reset();
md.update(b, offset, len);
byte[] digest = md.digest();
return DataTools.bytesToHex(digest);
}
catch (NoSuchAlgorithmException e) { }
return null;
}
/** Calculate the SHA-1 of a byte array. */
public static String sha1(byte[] b) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.reset();
md.update(b);
byte[] digest = md.digest();
return DataTools.bytesToHex(digest);
}
catch (NoSuchAlgorithmException e) { }
return null;
}
/** Calculate the MD5 of a byte array. */
public static String md5(byte[] b, int sizeX, int sizeY, int posX, int posY,
int width, int height, int bpp) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.reset();
int offset = 0;
for (int i = 0; i < height; i++) {
offset = (((posY + i) * sizeX) + posX) * bpp;
md.update(b, offset, width * bpp);
}
byte[] digest = md.digest();
return DataTools.bytesToHex(digest);
}
catch (NoSuchAlgorithmException e) { }
return null;
}
/** Calculate the MD5 of a byte array. */
public static String md5(byte[] b, int offset, int len) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.reset();
md.update(b, offset, len);
byte[] digest = md.digest();
return DataTools.bytesToHex(digest);
}
catch (NoSuchAlgorithmException e) { }
return null;
}
/** Calculate the MD5 of a byte array. */
public static String md5(byte[] b) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.reset();
md.update(b);
byte[] digest = md.digest();
return DataTools.bytesToHex(digest);
}
catch (NoSuchAlgorithmException e) { }
return null;
}
/** Returns true if a byte buffer of the given size will fit in memory. */
public static boolean canFitInMemory(long bufferSize) {
Runtime r = Runtime.getRuntime();
long mem = r.freeMemory() / 2;
int threadCount = 1;
try {
threadCount = Integer.parseInt(System.getProperty("testng.threadCount"));
}
catch (NumberFormatException e) { }
mem /= threadCount;
return bufferSize < mem && bufferSize <= Integer.MAX_VALUE;
}
/** Gets the quantity of used memory, in MB. */
public static long getUsedMemory() {
Runtime r = Runtime.getRuntime();
long mem = r.totalMemory() - r.freeMemory();
return mem >> 20;
}
/** Gets the class name sans package for the given object. */
public static String shortClassName(Object o) {
String name = o.getClass().getName();
int dot = name.lastIndexOf(".");
return dot < 0 ? name : name.substring(dot + 1);
}
/** Recursively generate a list of files to test. */
public static boolean isConfigFile(Location file, String configFileSuffix)
{
String configName = baseConfigName;
if (configFileSuffix.length() > 0) {
configName += ".";
configName += configFileSuffix;
}
String filename = file.getName();
return (filename.equals(configName) || filename.equals(baseConfigName));
}
/** Recursively generate a list of files to test. */
public static void getFiles(String root, List files,
final ConfigurationTree config, String toplevelConfig)
{
getFiles(root, files, config, toplevelConfig, null);
}
/** Recursively generate a list of files to test. */
public static void getFiles(String root, List files,
final ConfigurationTree config, String toplevelConfig, String[] subdirs)
{
getFiles(root, files, config, toplevelConfig, subdirs, "");
}
/**
* Retrieve an external configuration file given a root directory and test
* configuration
*/
public static String getExternalConfigFile(String root,
final ConfigurationTree config)
{
// Look for a configuration file under the configuration directory
String configRoot = config.relocateToConfig(root);
Location configFile = new Location(configRoot, baseConfigName);
if (configFile.exists()) {
return configFile.getAbsolutePath();
} else {
return null;
}
}
/**
* Retrieve an external symlinkedconfiguration file given a root directory
* and a test configuration
*/
public static String getExternalSymlinkConfigFile(String root,
final ConfigurationTree config)
{
// Look for a configuration file under the configuration directory
try {
String canonicalRoot = new Location(root).getCanonicalPath();
if (!root.equals(canonicalRoot)) {
String configCanonicalRoot = config.relocateToConfig(canonicalRoot);
Location configFile = new Location(configCanonicalRoot, baseConfigName);
if (configFile.exists()) {
return configFile.getAbsolutePath();
}
}
} catch (IOException e) {};
return null;
}
/** Recursively generate a list of files to test. */
public static void getFiles(String root, List files,
final ConfigurationTree config, String toplevelConfig, String[] subdirs,
String configFileSuffix)
{
Location f = new Location(root);
String[] subs = f.list();
if (subs == null) subs = new String[0];
if (subdirs != null) {
subs = subdirs;
}
boolean isToplevel =
toplevelConfig != null && new File(toplevelConfig).exists();
Arrays.sort(subs);
boolean isSymlinkConfig = false;
List<String> subsList = new ArrayList<String>();
if (config.getConfigDirectory() != null) {
String configFile = getExternalConfigFile(root, config);
if (configFile != null) {
LOGGER.debug("found config file: {}", configFile);
subsList.add(configFile);
} else {
configFile = getExternalSymlinkConfigFile(root, config);
if (configFile != null) {
LOGGER.debug("found symlinked config file: {}", configFile);
subsList.add(configFile);
isSymlinkConfig = true;
}
}
}
// make sure that if a config file exists, it is first on the list
for (int i=0; i<subs.length; i++) {
Location file = new Location(root, subs[i]);
if ((!isToplevel && isConfigFile(file, configFileSuffix)) ||
(isToplevel && subs[i].equals(toplevelConfig)))
{
if (config.getConfigDirectory() == null) {
LOGGER.debug("adding config file: {}", file.getAbsolutePath());
subsList.add(0, file.getAbsolutePath());
}
} else {
if (isSymlinkConfig) {
try {
subsList.add(file.getCanonicalPath());
} catch (IOException e) {
subsList.add(file.getAbsolutePath());
}
} else {
subsList.add(file.getAbsolutePath());
}
}
}
// special config file for the test suite
LOGGER.debug("\tconfig file");
try {
LOGGER.debug("Parsing {}:", subsList.get(0));
config.parseConfigFile(subsList.get(0));
}
catch (IOException exc) {
LOGGER.debug("", exc);
}
catch (Exception e) { }
Arrays.sort(subs, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
String s1 = o1.toString();
String s2 = o2.toString();
Configuration c1 = null;
Configuration c2 = null;
try {
c1 = config.get(s1);
}
catch (IOException e) { }
try {
c2 = config.get(s2);
}
catch (IOException e) { }
if (c1 == null && c2 != null) {
return 1;
}
else if (c1 != null && c2 == null) {
return -1;
}
return s1.compareTo(s2);
}
});
ImageReader typeTester = TestTools.getTestImageReader();
for (int i=0; i<subsList.size(); i++) {
Location file = new Location(subsList.get(i));
LOGGER.debug("Checking {}:", subsList.get(i));
if (isConfigFile(file, configFileSuffix)) {
continue;
}
else if (isIgnoredFile(subsList.get(i), config)) {
LOGGER.debug("\tignored");
continue;
}
else if (file.isDirectory()) {
LOGGER.debug("\tdirectory");
getFiles(subsList.get(i), files, config, null, null, configFileSuffix);
}
else if (!subsList.get(i).endsWith("readme.txt") &&
!subsList.get(i).endsWith("test_setup.ini")) {
if (typeTester.isThisType(subsList.get(i))) {
LOGGER.debug("\tOK");
files.add(file.getAbsolutePath());
}
else LOGGER.debug("\tunknown type");
}
file = null;
}
}
/** Determines if the given file should be ignored by the test suite. */
public static boolean isIgnoredFile(String file, ConfigurationTree config) {
if (file.indexOf(File.separator + ".") >= 0) return true; // hidden file
try {
Configuration c = config.get(file);
if (c == null) return false;
if (!c.doTest()) return true;
}
catch (IOException e) { }
catch (Exception e) { }
// HACK - heuristics to speed things up
if (file.endsWith(".oif.files")) return true; // ignore .oif folders
return false;
}
/**
* Iterates over every tile in a given pixel buffer based on the over arching
* dimensions and a requested maximum tile width and height.
* @param iteration Invoker to call for each tile.
* @param sizeX Width of the entire image.
* @param sizeY Height of the entire image.
* @param sizeZ Number of optical sections the image contains.
* @param sizeC Number of channels the image contains.
* @param sizeT Number of timepoints the image contains.
* @param tileWidth <b>Maximum</b> width of the tile requested. The tile
* request itself will be smaller than the original tile width requested if
* <code>x + tileWidth > sizeX</code>.
* @param tileHeight <b>Maximum</b> height of the tile requested. The tile
* request itself will be smaller if <code>y + tileHeight > sizeY</code>.
* @return The total number of tiles iterated over.
*/
public static int forEachTile(TileLoopIteration iteration,
int sizeX, int sizeY, int sizeZ, int sizeC,
int sizeT, int tileWidth, int tileHeight)
{
int tileCount = 0;
int x, y, w, h;
for (int t = 0; t < sizeT; t++) {
for (int c = 0; c < sizeC; c++) {
for (int z = 0; z < sizeZ; z++) {
for (int tileOffsetY = 0;
tileOffsetY < (sizeY + tileHeight - 1) / tileHeight;
tileOffsetY++) {
for (int tileOffsetX = 0;
tileOffsetX < (sizeX + tileWidth - 1) / tileWidth;
tileOffsetX++) {
x = tileOffsetX * tileWidth;
y = tileOffsetY * tileHeight;
w = tileWidth;
if (w + x > sizeX) {
w = sizeX - x;
}
h = tileHeight;
if (h + y > sizeY) {
h = sizeY - y;
}
iteration.run(z, c, t, x, y, w, h, tileCount);
tileCount++;
}
}
}
}
}
return tileCount;
}
/**
* A single iteration of a tile for each loop.
* @author Chris Allan <callan at blackcat dot ca>
* @since OMERO Beta-4.3.0
*/
public interface TileLoopIteration {
/**
* Invoke a single loop iteration.
* @param z Z section counter of the loop.
* @param c Channel counter of the loop.
* @param t Timepoint counter of the loop.
* @param x X offset within the plane specified by the section, channel and
* timepoint counters.
* @param y Y offset within the plane specified by the section, channel and
* timepoint counters.
* @param tileWidth Width of the tile requested. The tile request
* itself may be smaller than the original tile width requested if
* <code>x + tileWidth > sizeX</code>.
* @param tileHeight Height of the tile requested. The tile request
* itself may be smaller if <code>y + tileHeight > sizeY</code>.
* @param tileCount Counter of the tile since the beginning of the loop.
*/
void run(int z, int c, int t, int x, int y, int tileWidth,
int tileHeight, int tileCount);
}
/**
* Map the given file into memory.
*
* @return true if the mapping was successful.
*/
public static boolean mapFile(String id) throws IOException {
RandomAccessInputStream stream = new RandomAccessInputStream(id);
Runtime rt = Runtime.getRuntime();
long maxMem = rt.freeMemory();
long length = stream.length();
if (length < Integer.MAX_VALUE && length < maxMem) {
stream.close();
FileInputStream fis = new FileInputStream(id);
FileChannel channel = fis.getChannel();
ByteBuffer buf =
channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
ByteArrayHandle handle = new ByteArrayHandle(buf);
Location.mapFile(id, handle);
fis.close();
return true;
}
stream.close();
return false;
}
/**
* Return an ImageReader that is appropriate for testing.
* All constructed reader wrappers should use this ImageReader,
* as it removes any readers that aren't to be tested.
*/
public static ImageReader getTestImageReader() {
// Remove external SlideBook6Reader class for testing purposes
ImageReader ir = new ImageReader();
ir.getDefaultReaderClasses().removeClass(SlideBook6Reader.class);
return ir;
}
}