blob: 7cd0ca7a9a58e56931e6924765e20030de380f6e [file] [log] [blame]
/*=========================================================================
* Copyright (c) 2010-2014 Pivotal Software, Inc. All Rights Reserved.
* This product is protected by U.S. and international copyright
* and intellectual property laws. Pivotal products are covered by
* one or more patents listed at http://www.pivotal.io/patents.
*=========================================================================
*/
package com.gemstone.gemfire.codeAnalysis;
import static org.junit.Assert.fail;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import com.gemstone.gemfire.codeAnalysis.decode.CompiledClass;
import com.gemstone.gemfire.codeAnalysis.decode.CompiledField;
import com.gemstone.gemfire.codeAnalysis.decode.CompiledMethod;
import com.gemstone.gemfire.internal.GemFireVersion;
import com.gemstone.gemfire.test.junit.categories.IntegrationTest;
import com.gemstone.gemfire.util.test.TestUtil;
/**
* @author bruces
*
*/
@Category(IntegrationTest.class)
public class AnalyzeSerializablesJUnitTest {
/** all loaded classes */
protected static Map<String, CompiledClass> classes = new HashMap<String, CompiledClass>();
protected static boolean DISABLED = true;
private static boolean ClassesNotFound;
public AnalyzeSerializablesJUnitTest() {
}
@Before
public void loadClasses() throws Exception {
if (classes.size() > 0) {
return;
}
System.out.println("loadClasses starting");
String version = System.getProperty("java.runtime.version");
if (version == null || !version.startsWith("1.7")) {
// sanctioned info is based on a 1.7 compiler
System.out.println("AnalyzeSerializables requires a Java 7 but tests are running with v"+version);
DISABLED=true;
return;
}
List<String> excludedClasses = loadExcludedClasses(new File(TestUtil.getResourcePath(AnalyzeSerializablesJUnitTest.class, "excludedClasses.txt")));
List<String> openBugs = loadOpenBugs(new File(TestUtil.getResourcePath(AnalyzeSerializablesJUnitTest.class, "openBugs.txt")));
excludedClasses.addAll(openBugs);
loadJGroupsJar(excludedClasses);
String cp = System.getProperty("java.class.path");
System.out.println("java classpath is " + cp);
System.out.flush();
String[] entries = cp.split(File.pathSeparator);
String buildDirName =
"gemfire-core"+File.separatorChar
+"build"+File.separatorChar
+"classes"+File.separatorChar
+"main";
String buildDir = null;
for (int i=0; i<entries.length && buildDir==null; i++) {
System.out.println("examining '" + entries[i] + "'");
System.out.flush();
if (entries[i].endsWith(buildDirName)) {
buildDir = entries[i];
}
}
if (buildDir != null) {
System.out.println("loading class files from " + buildDir);
System.out.flush();
long start = System.currentTimeMillis();
loadClassesFromBuild(new File(buildDir), excludedClasses);
long finish = System.currentTimeMillis();
System.out.println("done loading " + classes.size() + " classes. elapsed time = "
+ (finish-start)/1000 + " seconds");
}
else {
fail("unable to find geode classes");
}
DISABLED = false;
}
@AfterClass
public static void cleanup() {
if (classes != null) {
classes.clear();
}
}
private static void loadJGroupsJar(List<String> excludedClasses) throws Exception {
System.out.println("loadJGroupsJar starting");
String cp = System.getProperty("java.class.path");
System.out.println("java classpath is " + cp);
System.out.flush();
String[] entries = cp.split(File.pathSeparator);
String gfejgroupsjar = null;
String gfejgroupsjarname = GemFireVersion.getGemFireJGroupsJarFileName();
for (int i=0; i<entries.length; i++) {
System.out.println("examining '" + entries[i] + "'");
System.out.flush();
if (entries[i].endsWith(gfejgroupsjarname)) {
gfejgroupsjar = entries[i];
break;
}
}
if (gfejgroupsjar != null) {
System.out.println("loading class files from " + gfejgroupsjar);
System.out.flush();
loadClasses(new File(gfejgroupsjar), excludedClasses);
}
else {
fail("unable to find jgroups jar");
}
DISABLED = false;
}
protected static List<String> loadExcludedClasses(File exclusionsFile) throws Exception {
List<String> excludedClasses = new LinkedList<String>();
FileReader fr = new FileReader(exclusionsFile);
BufferedReader br = new BufferedReader(fr);
try {
String line;
while ((line = br.readLine()) != null) {
line = line.trim();
if (line.length() > 0 && !line.startsWith("#")) {
excludedClasses.add(line);
}
}
} finally {
fr.close();
}
return excludedClasses;
}
protected static List<String> loadOpenBugs(File exclusionsFile) throws Exception {
List<String> excludedClasses = new LinkedList<String>();
FileReader fr = new FileReader(exclusionsFile);
BufferedReader br = new BufferedReader(fr);
try {
String line;
// each line should have bug#,full-class-name
while ((line = br.readLine()) != null) {
line = line.trim();
if (line.length() > 0 && !line.startsWith("#")) {
String[] split = line.split(",");
if (split.length != 2) {
DISABLED = true; // don't run the other tests
fail("unable to load classes due to misformatted line in openBugs.txt: " + line);
}
excludedClasses.add(line.split(",")[1].trim());
}
}
} finally {
fr.close();
}
return excludedClasses;
}
private static void removeExclusions(Map<String, CompiledClass> classes, List<String> exclusions) {
for (String exclusion: exclusions) {
exclusion = exclusion.replace('.', '/');
classes.remove(exclusion);
}
}
@Test
public void testDataSerializables() throws Exception {
System.out.println("testDataSerializables starting");
if (ClassesNotFound) {
System.out.println("... test not run due to not being able to locate product class files");
return;
}
if (DISABLED) {
System.out.println("... test is disabled");
return;
}
String compareToFileName = TestUtil.getResourcePath(getClass(), "sanctionedDataSerializables.txt");
String storeInFileName = "actualDataSerializables.dat";
File storeInFile = new File(storeInFileName);
if (storeInFile.exists() && !storeInFile.canWrite()) {
throw new RuntimeException("can't write " + storeInFileName);
}
List<ClassAndMethods> toDatas = findToDatasAndFromDatas();
CompiledClassUtils.storeClassesAndMethods(toDatas, storeInFile);
File compareToFile = new File(compareToFileName);
if (!compareToFile.exists()) {
throw new RuntimeException("can't find " + compareToFileName);
}
if (!compareToFile.canRead()) {
throw new RuntimeException("can't read " + compareToFileName);
}
List<ClassAndMethodDetails> goldRecord = CompiledClassUtils.loadClassesAndMethods(compareToFile);
Collections.sort(goldRecord);
String diff = CompiledClassUtils.diffSortedClassesAndMethods(goldRecord, toDatas);
if (diff.length() > 0) {
System.out.println("++++++++++++++++++++++++++++++testDataSerializables found discrepencies++++++++++++++++++++++++++++++++++++");
System.out.println(diff);
fail(diff+"\n\nIf the class is not persisted or sent over the wire add it to the excludedClasses.txt file in the "
+ "\ncom/gemstone/gemfire/codeAnalysis directory. Otherwise if this doesn't "
+ "\nbreak backward compatibility move the file actualDataSerializables.dat to the codeAnalysis "
+ "\ntest directory and rename to sanctionedDataSerializables.txt");
}
}
@Test
public void testSerializables() throws Exception {
System.out.println("testSerializables starting");
System.out.flush();
if (ClassesNotFound) {
System.out.println("... test not run due to not being able to locate product class files");
return;
}
if (DISABLED) {
System.out.println("... test is disabled");
return;
}
String compareToFileName = TestUtil.getResourcePath(getClass(), "sanctionedSerializables.txt");
File compareToFile = new File(compareToFileName);
String storeInFileName = "actualSerializables.dat";
File storeInFile = new File(storeInFileName);
if (storeInFile.exists() && !storeInFile.canWrite()) {
throw new RuntimeException("can't write " + storeInFileName);
}
List<ClassAndVariables> serializables = findSerializables();
reset();
CompiledClassUtils.storeClassesAndVariables(serializables, storeInFile);
if (!compareToFile.exists()) {
throw new RuntimeException("can't find " + compareToFileName);
}
if (!compareToFile.canRead()) {
throw new RuntimeException("can't read " + compareToFileName);
}
List<ClassAndVariableDetails> goldRecord = CompiledClassUtils.loadClassesAndVariables(compareToFile);
Collections.sort(goldRecord);
String diff = CompiledClassUtils.diffSortedClassesAndVariables(goldRecord, serializables);
classes.clear();
if (diff.length() > 0) {
System.out.println("++++++++++++++++++++++++++++++testSerializables found discrepencies++++++++++++++++++++++++++++++++++++");
System.out.println(diff);
fail(diff+"\n\nIf the class is not persisted or sent over the wire add it to the excludedClasses.txt file in the "
+ "\n/com/gemstone/gemfire/codeAnalysis/ directory. Otherwise if this doesn't "
+ "\nbreak backward compatibility move the file actualSerializables.dat to the "
+ "\ncodeAnalysis test directory and rename to sanctionedSerializables.txt");
}
}
/**
* load the classes from the given files and directories
* @param excludedClasses names of classes to exclude
*/
public static void loadClasses(String directory, boolean recursive, List<String> excludedClasses) {
String[] filenames = new String[]{ directory };
List<File> classFiles = CompiledClassUtils.findClassFiles("", filenames, recursive);
Map<String, CompiledClass> newClasses = CompiledClassUtils.parseClassFiles(classFiles);
removeExclusions(newClasses, excludedClasses);
classes.putAll(newClasses);
}
public static void loadClassesFromBuild(File buildDir, List<String> excludedClasses) {
Map<String, CompiledClass> newClasses = CompiledClassUtils.parseClassFilesInDir(buildDir);
removeExclusions(newClasses, excludedClasses);
classes.putAll(newClasses);
}
/**
* load the classes from the given jar file
*/
public static void loadClasses(File jar, List<String> excludedClasses) {
Map<String, CompiledClass> newClasses = CompiledClassUtils.parseClassFilesInJar(jar);
removeExclusions(newClasses, excludedClasses);
classes.putAll(newClasses);
}
/**
* clears all loaded classes
*/
public void reset() {
classes.clear();
}
public List<ClassAndMethods> findToDatasAndFromDatas() {
List<ClassAndMethods> result = new ArrayList<ClassAndMethods>();
for (Map.Entry<String, CompiledClass> dentry: classes.entrySet()) {
CompiledClass dclass = dentry.getValue();
ClassAndMethods entry = null;
for (int i=0; i<dclass.methods.length; i++) {
CompiledMethod method = dclass.methods[i];
if (!method.isAbstract() && method.descriptor().equals("void")) {
String name = method.name();
if (name.startsWith("toData") || name.startsWith("fromData")) {
if (entry == null) {
entry = new ClassAndMethods(dclass);
}
entry.methods.put(method.name(), method);
}
}
}
if (entry != null) {
result.add(entry);
}
}
Collections.sort(result);
return result;
}
public List<ClassAndVariables> findSerializables() {
List<ClassAndVariables> result = new ArrayList<ClassAndVariables>(2000);
for (Map.Entry<String, CompiledClass> entry: classes.entrySet()) {
CompiledClass dclass = entry.getValue();
System.out.println("processing class " + dclass.fullyQualifiedName());
System.out.flush();
if (!dclass.isInterface() && dclass.isSerializableAndNotDataSerializable()) {
ClassAndVariables cav = new ClassAndVariables(dclass);
for (int i=0; i<dclass.fields_count; i++) {
CompiledField f = dclass.fields[i];
if (!f.isStatic() && !f.isTransient()) {
cav.variables.put(f.name(), f);
}
}
result.add(cav);
}
}
Collections.sort(result);
return result;
}
}