- Added programmatic access to BuildID and Version through methods on FModel.getBuildInfo().

- Added class forge.model.BuildInfo with unit test.
- Added unit test for forge.model.FModel.
- Added exception class forge.model.MultipleForgeJarsFoundError.
- Updated forge.view.swing.Main with some preliminary code for progress monitoring.
- Reduced CheckStyle, FindBugs, and PMD violations in these files and in net.slightlymagic.braids.util.testng.BraidsAssertFunctions.
- Added missing package-info in net.slightlymagic.braids.util.testng.
This commit is contained in:
Braids
2011-08-12 14:47:37 +00:00
parent d8c7e407c5
commit 10431c521e
9 changed files with 609 additions and 37 deletions

5
.gitattributes vendored
View File

@@ -9665,7 +9665,9 @@ src/main/java/forge/gui/input/Input_PayManaCostUtil.java svneol=native#text/plai
src/main/java/forge/gui/input/Input_PayManaCost_Ability.java svneol=native#text/plain
src/main/java/forge/gui/input/package-info.java svneol=native#text/plain
src/main/java/forge/gui/package-info.java svneol=native#text/plain
src/main/java/forge/model/BuildInfo.java -text
src/main/java/forge/model/FModel.java svneol=native#text/plain
src/main/java/forge/model/MultipleForgeJarsFoundError.java -text
src/main/java/forge/model/package-info.java svneol=native#text/plain
src/main/java/forge/package-info.java svneol=native#text/plain
src/main/java/forge/properties/ForgePreferences.java svneol=native#text/plain
@@ -9780,5 +9782,8 @@ src/test/java/forge/deck/generate/GenerateConstructedDeckTest.java svneol=native
src/test/java/forge/deck/generate/GenerateConstructedMultiColorDeckTest.java svneol=native#text/plain
src/test/java/forge/gui/ListChooserTest.java svneol=native#text/plain
src/test/java/forge/gui/game/CardDetailPanelTest.java svneol=native#text/plain
src/test/java/forge/model/BuildInfoTest.java -text
src/test/java/forge/model/FModelTest.java -text
src/test/java/net/slightlymagic/braids/util/testng/BraidsAssertFunctions.java svneol=native#text/plain
src/test/java/net/slightlymagic/braids/util/testng/LICENSE.txt svneol=native#text/plain
src/test/java/net/slightlymagic/braids/util/testng/package-info.java -text

View File

@@ -0,0 +1,234 @@
package forge.model;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Provides access to information about the current version and build ID.
*/
public class BuildInfo {
/** Exception-free means of getting the ASCII Charset. */
public static final Charset US_ASCII_CHARSET = Charset.forName("US-ASCII");
/** Convenience for file.separator. */
private static final String FILE_SEP = System.getProperty("file.separator");
/** Convenience for path.separator. */
private static final String PATH_SEP = System.getProperty("path.separator");
private static final Pattern FORGE_JAR_REGEX_2G = // NOPMD by Braids on 8/12/11 10:18 AM
Pattern.compile("^(.*" + Pattern.quote(FILE_SEP) + ")?"
+ Pattern.quote("forge-")
+ "([^" + Pattern.quote(FILE_SEP) + Pattern.quote(PATH_SEP) + "]*)"
+ Pattern.quote("-with-dependencies.jar") + "$",
Pattern.CASE_INSENSITIVE);
private transient String pathToForgeJar;
/**
* Construct a standard BuildInfo object.
*
* Package access is intentional for unit testing.
*
* @see FModel.getBuildInfo()
*/
BuildInfo() {
// empty
}
/**
* Unit-testable constructor which allows a specific jar file to be set.
*
* Dependency injection! Relax, this won't hurt a bit.
*
* @param pathToMockJarFile where to find the mock Forge jar
*/
public BuildInfo(final String pathToMockJarFile) {
pathToForgeJar = pathToMockJarFile;
}
/**
* Get the current build ID for Forge.
*
* @return a String representing the build identifier, or null if we could
* not determine the value.
*/
public final String getBuildID() {
String manifestResult;
String result;
try {
manifestResult = getManifestAttribute("Implementation-Build");
} catch (IOException exn1) {
manifestResult = null; // NOPMD by Braids on 8/12/11 10:21 AM
}
if (manifestResult == null) {
// Try getting the SVN version number by running the svnversion
// command. This is a long shot, but it works on some developers'
// systems.
Process proc = null;
BufferedReader reader = null;
try {
proc = Runtime.getRuntime().exec("svnversion");
final InputStream procStdoutStream = proc.getInputStream();
final Reader procReader = new InputStreamReader(procStdoutStream, US_ASCII_CHARSET);
reader = new BufferedReader(procReader);
result = reader.readLine(); // may be null
} catch (IOException exn2) {
result = null; // NOPMD by Braids on 8/12/11 10:21 AM
} finally {
try {
reader.close();
} catch (Throwable exn3) { // NOPMD by Braids on 8/12/11 10:21 AM
// ignored
}
try {
proc.destroy();
} catch (Throwable exn4) { // NOPMD by Braids on 8/12/11 10:21 AM
// ignored
}
}
}
else {
result = manifestResult;
}
return result;
}
/**
* Get the current version of Forge.
*
* @return a String representing the version specifier, or "SVN" if
* unknown.
*/
public final String getVersion() {
String manifestResult;
String result;
try {
manifestResult = getManifestAttribute("Implementation-Version");
} catch (IOException exn) {
manifestResult = null; // NOPMD by Braids on 8/12/11 10:21 AM
}
if (manifestResult == null) {
result = "SVN";
}
else {
result = manifestResult;
}
return result;
}
/**
* Fetch an attribute from the Forge main jar's manifest.
*
* @param manifestAttrName the name of the attribute you want from the manifest
* @return the attribute's value, which may be empty or null
* @throws IOException if a (unique) Forge jar could not be found
*/
protected final String getManifestAttribute(final String manifestAttrName) throws IOException {
String result = null;
JarFile jar = null;
InputStream manifestStream = null;
try {
if (pathToForgeJar == null) {
// We're definitely not unit testing. Try to load from the
// currently running jar.
manifestStream = ClassLoader.getSystemResourceAsStream("META-INF/MANIFEST.MF");
final Manifest manifest = new Manifest(manifestStream);
result = getMainManifestAttribute(manifest, manifestAttrName);
}
if (result == null && pathToForgeJar == null) {
// Try to find a unique Forge jar in the class path.
final String classPath = System.getProperty("java.class.path");
final String[] paths = classPath.split(PATH_SEP);
for (String path : paths) {
final Matcher matcher = FORGE_JAR_REGEX_2G.matcher(path);
if (matcher.matches()) {
if (pathToForgeJar == null) {
pathToForgeJar = path;
}
else {
// Error: we found more than one.
pathToForgeJar = null; // NOPMD by Braids on 8/12/11 10:21 AM
throw new MultipleForgeJarsFoundError(
"Classpath = " + System.getProperty("java.class.path"));
}
}
}
}
if (result == null && pathToForgeJar == null) {
throw new FileNotFoundException(
"There is nothing matching forge-*-with-dependencies.jar in the class path.");
}
if (result == null) {
jar = new JarFile(pathToForgeJar);
final Manifest manifest = jar.getManifest();
if (manifest == null) {
throw new IOException("Forge jar at <<" + pathToForgeJar + ">> has no manifest.");
}
result = getMainManifestAttribute(manifest, manifestAttrName);
}
}
finally {
try {
manifestStream.close();
} catch (Throwable ignored) { // NOPMD by Braids on 8/12/11 10:21 AM
// ignored
}
try {
jar.close();
} catch (Throwable ignored) { // NOPMD by Braids on 8/12/11 10:21 AM
// ignored
}
}
return result;
}
/**
* Convience method for fetching an attribute from the main section of a
* jar's manifest.
*
* @param manifest the manifest that provides attributes
* @param manifestAttrName the name of the attribute to fetch
* @return the value of the attribute, or null if not set
*/
protected final String getMainManifestAttribute(final Manifest manifest, final String manifestAttrName) {
final Attributes atts = manifest.getMainAttributes();
return atts.getValue(manifestAttrName);
}
}

View File

@@ -3,9 +3,12 @@ package forge.model;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
//import net.slightlymagic.braids.util.progress_monitor.BaseProgressMonitor;
import net.slightlymagic.braids.util.progress_monitor.BraidsProgressMonitor;
import arcane.util.MultiplexOutputStream;
/**
@@ -18,24 +21,89 @@ import arcane.util.MultiplexOutputStream;
* this class must be either private or public static final.
*/
public class FModel {
//private static final int NUM_INIT_PHASES = 1;
private final transient OutputStream logFileStream;
private final transient PrintStream oldSystemOut;
private final transient PrintStream oldSystemErr;
private BuildInfo buildInfo;
/**
* Constructor.
*
* @param theMonitor a progress monitor (from the View) that shows the
* progress of the model's initialization.
*
* @throws FileNotFoundException if we could not find or write to the log file.
*/
public FModel() throws FileNotFoundException {
public FModel(final BraidsProgressMonitor theMonitor) throws FileNotFoundException {
/* To be implemented later. -Braids
BraidsProgressMonitor monitor;
if (theMonitor == null) {
monitor = new BaseProgressMonitor(NUM_INIT_PHASES, 1);
}
else {
monitor = theMonitor;
}
*/
final File logFile = new File("forge.log");
final boolean deleteSucceeded = logFile.delete();
if (logFile.exists() && !deleteSucceeded) {
if (logFile.exists() && !deleteSucceeded && logFile.length() != 0) {
throw new IllegalStateException("Could not delete existing logFile:" + logFile.getAbsolutePath());
}
// This used to be a BufferedOutputStream, but that seems inappropriate for a log. --Braids
final OutputStream logFileStream = new FileOutputStream(logFile);
logFileStream = new FileOutputStream(logFile);
oldSystemOut = System.out;
System.setOut(new PrintStream(new MultiplexOutputStream(System.out, logFileStream), true));
oldSystemErr = System.err;
System.setErr(new PrintStream(new MultiplexOutputStream(System.err, logFileStream), true));
setBuildInfo(new BuildInfo());
}
/**
* Destructor for FModel.
* @throws Throwable indirectly
*/
@Override
protected final void finalize() throws Throwable {
close();
super.finalize();
}
/**
* Opposite of constructor; resets all system resources and closes the
* log file.
*/
public final void close() {
System.setOut(oldSystemOut);
System.setErr(oldSystemErr);
try {
logFileStream.close();
} catch (IOException e) { // NOPMD by Braids on 8/12/11 10:25 AM
// ignored
}
}
/**
* Getter for buildInfo.
*
* @return the buildInfo
*/
public final BuildInfo getBuildInfo() {
return buildInfo;
}
/**
* Setter for buildInfo.
*
* @param neoBuildInfo the buildInfo to set
*/
protected final void setBuildInfo(final BuildInfo neoBuildInfo) {
this.buildInfo = neoBuildInfo;
}
}

View File

@@ -0,0 +1,23 @@
package forge.model;
//import java.io.IOException;
/**
* Exception thrown by model when it is trying to find a single forge jar, but
* it finds more than one.
*/
public class MultipleForgeJarsFoundError extends RuntimeException {
/** Automatically generated. */
private static final long serialVersionUID = 8899307272033517172L;
/**
* Create an exception with a message.
*
* @param message the message, which could be the System's class path.
*/
public MultipleForgeJarsFoundError(final String message) {
super(message);
}
}

View File

@@ -42,7 +42,7 @@ public final class Main {
public static void main(final String[] args) {
ExceptionHandler.registerErrorHandling();
try {
final FModel model = new FModel();
final FModel model = new FModel(null);
Singletons.setModel(model);
final FView view = new ApplicationView();
Singletons.setView(view);

View File

@@ -0,0 +1,180 @@
package forge.model;
import static net.slightlymagic.braids.util.testng.BraidsAssertFunctions.assertThrowsException;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import net.slightlymagic.braids.util.ClumsyRunnable;
import org.testng.annotations.Test;
/**
* Test the BuildInfo class.
*/
public class BuildInfoTest {
/** System property name for the class path. */
private static final String JAVA_CLASS_PATH = "java.class.path";
/** Manifest attribute name for the Build ID. */
private static final String MF_ATTR_NAME_BUILD = "Implementation-Build"; // NOPMD by Braids on 8/12/11 10:29 AM
/** Manifest attribute name for the version string. */
private static final String MF_ATTR_NAME_VERSION = "Implementation-Version"; // NOPMD by Braids on 8/12/11 10:29 AM
/**
* Test BuildInfo using a mock jar file.
* @throws IOException rarely
*/
@Test
public final void test_BuildInfo_mockJar() throws IOException { // NOPMD by Braids on 8/12/11 10:26 AM
File jarAsFile = null;
try {
jarAsFile = makeTmpJarWithManifest("BuildInfoTest-", ".jar",
new String[] {MF_ATTR_NAME_VERSION, "1.2.42", MF_ATTR_NAME_BUILD, "4200"});
final BuildInfo info = new BuildInfo(jarAsFile.getAbsolutePath());
final String actualVersion = info.getVersion();
assertEquals(actualVersion, "1.2.42", "versions match");
final String actualBuildID = info.getBuildID();
assertEquals(actualBuildID, "4200", "build IDs match");
} finally {
assertTrue(jarAsFile.delete(), "attempting to delete temporary jar file");
}
}
/**
* Test BuildInfo with one mock forge jar placed in the class path.
* @throws IOException indirectly
*/
@Test
public final void test_BuildInfo_oneJarInCP() throws IOException { // NOPMD by Braids on 8/12/11 10:26 AM
final String origClassPath = System.getProperty(JAVA_CLASS_PATH);
File jarAsFile = null;
try {
jarAsFile = makeTmpJarWithManifest("forge-BuildInfoTest-", "-with-dependencies.jar",
new String[] {MF_ATTR_NAME_VERSION, "1.2.43", MF_ATTR_NAME_BUILD, "4201"});
System.setProperty(JAVA_CLASS_PATH,
jarAsFile.getAbsolutePath() + System.getProperty("path.separator") + origClassPath);
final BuildInfo info = new BuildInfo();
final String actualVersion = info.getVersion();
assertEquals(actualVersion, "1.2.43", "versions match");
final String actualBuildID = info.getBuildID();
assertEquals(actualBuildID, "4201", "build IDs match");
} finally {
assertTrue(jarAsFile.delete(), "attempting to delete temporary jar file");
System.setProperty(JAVA_CLASS_PATH, origClassPath);
}
}
/**
* Test BuildInfo with two mock forge jars placed in the class path; this
* is an error condition.
*
* @throws IOException indirectly
*/
@Test
public final void test_BuildInfo_twoJarsInCP() throws IOException { // NOPMD by Braids on 8/12/11 10:26 AM
final String origClassPath = System.getProperty(JAVA_CLASS_PATH);
File jarAsFile1 = null;
File jarAsFile2 = null;
try {
jarAsFile1 = makeTmpJarWithManifest("forge-BuildInfoTest-1-", "-with-dependencies.jar",
new String[] {MF_ATTR_NAME_VERSION, "1.1.1", MF_ATTR_NAME_BUILD, "1111"});
jarAsFile2 = makeTmpJarWithManifest("forge-BuildInfoTest-2-", "-with-dependencies.jar",
new String[] {MF_ATTR_NAME_VERSION, "2.2.2", MF_ATTR_NAME_BUILD, "2222"});
final String pathSep = System.getProperty("path.separator");
System.setProperty(JAVA_CLASS_PATH,
jarAsFile1.getAbsolutePath() + pathSep
+ jarAsFile2.getAbsolutePath() + pathSep
+ origClassPath);
final BuildInfo info = new BuildInfo();
assertThrowsException(MultipleForgeJarsFoundError.class,
new ClumsyRunnable() {
public void run() throws Exception { // NOPMD by Braids on 8/12/11 10:29 AM
info.getBuildID();
}
});
} finally {
assertTrue(jarAsFile1.delete(), "attempting to delete 1st temporary jar");
assertTrue(jarAsFile2.delete(), "attempting to delete 2nd temporary jar");
System.setProperty(JAVA_CLASS_PATH, origClassPath);
}
}
/**
* Helper method to create jar files at specific locations with specific
* name-value pairs in their manifests.
*
* @param jarLocation where to create the jar file
* @param nameValuePairs has the form {"name1", "value1", "name2", "value2", ...}
*/
private File makeTmpJarWithManifest(final String fileNamePrefix, final String fileNameSuffix,
final String[] nameValuePairs)
throws IOException
{
if (nameValuePairs.length % 2 != 0) {
throw new IllegalArgumentException("nameValuePairs must contain an even number of elements.");
}
File result = null;
FileOutputStream fileOut = null;
ZipOutputStream zipOut = null;
try {
result = File.createTempFile(fileNamePrefix, fileNameSuffix);
fileOut = new FileOutputStream(result);
zipOut = new ZipOutputStream(fileOut);
final ZipEntry zipEntry = new ZipEntry("META-INF/MANIFEST.MF");
zipOut.putNextEntry(zipEntry);
zipOut.write(toASCII("Manifest-Version: 1.3\n"));
for (int ix = 0; ix < nameValuePairs.length; ix += 2) {
zipOut.write(toASCII(nameValuePairs[ix]));
zipOut.write(toASCII(": "));
zipOut.write(toASCII(nameValuePairs[ix + 1]));
zipOut.write(toASCII("\n"));
}
zipOut.write(toASCII("\n"));
zipOut.closeEntry();
} finally {
if (zipOut != null) {
zipOut.close();
}
if (fileOut != null) {
fileOut.close();
}
}
return result;
}
private byte[] toASCII(final String str) {
return str.getBytes(BuildInfo.US_ASCII_CHARSET);
}
}

View File

@@ -0,0 +1,58 @@
package forge.model;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import java.io.FileNotFoundException;
import org.testng.annotations.Test;
/**
* Tests FModel.
*/
public class FModelTest {
/**
* Test constructor, close, and construct again.
* @throws FileNotFoundException if something is really wrong
*/
@Test
public final void test_ctor_close_ctor() throws FileNotFoundException { // NOPMD by Braids on 8/12/11 10:36 AM
final FModel modelA = new FModel(null);
assertNotNull(modelA, "modelA is not null");
modelA.close();
System.err.println("log test"); // NOPMD by Braids on 8/12/11 10:36 AM
final FModel modelB = new FModel(null);
assertNotNull(modelB, "modelB is not null");
}
/**
* Test getVersion.
* @throws FileNotFoundException if something is really wrong
*/
@Test
public final void test_getVersion() throws FileNotFoundException { // NOPMD by Braids on 8/12/11 10:36 AM
final FModel model = new FModel(null);
final String version = model.getBuildInfo().getVersion();
model.close();
assertEquals(version, "SVN", "version is default");
}
/**
* Test getBuildID.
* @throws FileNotFoundException if something is really wrong
*/
@Test
public final void test_getBuildID() throws FileNotFoundException { // NOPMD by Braids on 8/12/11 10:36 AM
final FModel model = new FModel(null);
// Just test for an unexpected exception.
model.getBuildInfo().getBuildID();
model.close();
}
}

View File

@@ -1,41 +1,42 @@
package net.slightlymagic.braids.util.testng;
import net.slightlymagic.braids.util.ClumsyRunnable;
import org.testng.Assert;
import org.testng.annotations.Test;
/**
* A collection of assert functions that go with TestNG.
*/
@Test(groups = {"UnitTest"})
public class BraidsAssertFunctions {
public final class BraidsAssertFunctions {
/** Do not instantiate.*/
private BraidsAssertFunctions() {;}
/** Do not instantiate.*/
private BraidsAssertFunctions() {
//empty
}
/**
* Assert that a function (ClumsyRunnable) throws the given exception, or
* a subclass of it.
*
* @param exnClass the exception we expect
* @param withScissors the code to run
*/
public static void assertThrowsException(
@SuppressWarnings("rawtypes") Class exnClass,
ClumsyRunnable withScissors)
{
try {
withScissors.run();
}
catch (Exception exn) {
if (!exnClass.isInstance(exn)) {
Assert.fail("caught exception " + exn.getClass().getName() +
", but expected " + exnClass.getName());
}
/**
* Assert that a function (ClumsyRunnable) throws the given exception, or
* a subclass of it.
*
* @param exnClass the exception we expect
* @param withScissors the code to run
*/
public static void assertThrowsException(
@SuppressWarnings("rawtypes") final Class exnClass,
final ClumsyRunnable withScissors)
{
try {
withScissors.run();
}
catch (Exception exn) {
if (!exnClass.isInstance(exn)) {
Assert.fail("caught exception " + exn.getClass().getName()
+ ", but expected " + exnClass.getName());
}
return; //success
}
return; //success
}
Assert.fail("expected exception " + exnClass.getName() + ", but none was thrown");
}
Assert.fail("expected exception " + exnClass.getName() + ", but none was thrown");
}
}

View File

@@ -0,0 +1,3 @@
/** Utilities for TestNG tests. Licensed under both Apache 2.0 and GNU Public Licenses. */
package net.slightlymagic.braids.util.testng;