Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Cassandra using run_ctest_java module #37

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
489 changes: 489 additions & 0 deletions core/patch/cassandra/interception.patch

Large diffs are not rendered by default.

2,400 changes: 2,400 additions & 0 deletions core/patch/cassandra/logging.patch

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions core/run_ctest_java/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

# Maven (from: https://github.com/github/gitignore/blob/master/Maven.gitignore)
target/
build/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
Expand All @@ -32,3 +33,6 @@ release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties

# Target Project
app/
11 changes: 11 additions & 0 deletions core/run_ctest_java/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,16 @@ mvn exec:java -q \
-Dproject.args=-q
```

Example with 1 Success and 1 Fail. `testCqlBatch_MultipleTablesAuditing` is failed because `num_tokens must be >= 1`
```
mvn exec:java -q \
-Dproject.name=cassandra \
-Dproject.path=app/cassandra \
-Dmapping.path=resources/supported/cassandra/param_unset_getter_map.json \
-Dconf.file=examples/example-config.yaml \
-Dtest.file=examples/example-test.txt \
-Dproject.props=use.jdk11=true
```

## How To Support New Project
Check out instructions in [here](./docs/Support-New-Project.md)
2 changes: 1 addition & 1 deletion core/run_ctest_java/docs/Support-New-Project.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Using the following instructions to add support for new project.
## Instrument Target Project
- You may need to modify the source code of target project, so the target project will load the modified configuration values when it is running.
- One way is to use the project build-in function to load modified configuration files during runtime. Check out example [git patch](../../patch/hadoop-common/interception.patch).
- Another way is to first load the default configuration values as a Map object then load the modified configuration files as another Map object, and merge them together, then pass the merged one into downstream operation.
- Another way is to first load the default configuration values as a Map object then load the modified configuration files as another Map object, and merge them together, then pass the merged one into downstream operation. Check out example [git patch](../../patch/cassandra/interception.patch)
- Pack the modification as a git patch for future reference.

## Implement CTest Logic
Expand Down
7 changes: 5 additions & 2 deletions core/run_ctest_java/resources/supported/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# CTest Supported Project
The following is a list of currently supported project/module by CTest. You can find the general information about the specific project/module in their own directory.
- Note: the format of supported modules is `parent project/module name`.
- Note: the naming format of modules is `parent-project/module-name`.

Modules:
Supported Projects:
- [cassandra](./cassandra/README.md)

Supported Modules:
- [hadoop/hadoop-common](./hadoop-common/README.md)
- [hadoop/hadoop-hdfs](./hadoop-hdfs/README.md)
20 changes: 20 additions & 0 deletions core/run_ctest_java/resources/supported/cassandra/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# How to set up Cassandra for CTest
Prerequisites:
- Java 11
- Ant 1.10.7

Steps:
1. run setup script, `./setup.sh`
2. (Optional) go to cassandra directory and verify setup, `ant testsome -Dtest.name=org.apache.cassandra.hints.HintsCatalogTest -Dtest.methods=deleteHintsTest -Duse.jdk11=true`

# Additional Parameters For CTest
For running CTest, please refer to [doc](../../../README.md#how-to-run-ctest)

## project.props
- `use.jdk11=true`
- required

## project.args
- `-q`
- recommended
- suppress INFO level log
30,719 changes: 30,719 additions & 0 deletions core/run_ctest_java/resources/supported/cassandra/param_unset_getter_map.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions core/run_ctest_java/resources/supported/cassandra/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
APP_PATH=$(git rev-parse --show-toplevel)
[ ! -d "$APP_PATH/core/run_ctest_java/app/cassandra" ] && git clone https://github.com/CornDavid5/cassandra.git "$APP_PATH/core/run_ctest_java/app/cassandra"
cd "$APP_PATH/core/run_ctest_java/app/cassandra"
git fetch && git checkout ctest-injection
CASSANDRA_USE_JDK11=true ant
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ public static CTestRunnable getCTestRunner(final String project) {
return new HadoopCommon();
case "hadoop-hdfs":
return new HadoopHDFS();
case "cassandra":
return new Cassandra();
default:
throw new IllegalStateException(String.format(
"run-ctest doesn't support {}", project));
"run-ctest doesn't support %s", project));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
package uiuc.xlab.openctest.runctest.supported;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.javatuples.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import uiuc.xlab.openctest.runctest.interfaces.CTestRunnable;

public final class Cassandra implements CTestRunnable {
/**
* logger for this class.
*/
private static final Logger LOGGER =
LoggerFactory.getLogger(Cassandra.class);
/**
* JUnit file name template.
*/
private static final String JUNIT_OUTPUT_XML = "TEST-@-#.xml";
/**
* root path of the target project.
*/
private Path rootPath;
/**
* path to junit directory.
*/
private Path junitPath;
/**
* path to the ctest injected configuration file.
*/
private Path configInjectionPath;

@Override
public void setProjectRootPath(final Path targetPath) {
this.rootPath = targetPath;
junitPath = Path.of(targetPath.toString(), "build/test/output");
configInjectionPath = Path.of(
targetPath.toString(),
"test/conf",
"ctest-injection.yaml");
}

@Override
public void injectConfig(final Map<String, Object> updatedConfig) {
// cassandra using yaml to store configuration
try {
// delete old ctest-injection.yaml file
Files.deleteIfExists(configInjectionPath);
} catch (IOException e) {
e.printStackTrace();
}

// write out
try (FileWriter fw = new FileWriter(configInjectionPath.toFile())) {
for (Map.Entry<String, Object> conf : updatedConfig.entrySet()) {
fw.write(String.format(
"%s: %s%n",
conf.getKey(),
conf.getValue().toString()));
}

LOGGER.info(
"Injected modified config into: {}", configInjectionPath);
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void runCTest(
final Map<String, String> context,
final Set<String> affectedTest) {
Map<String, String> classTestMap = getClassTestMap(affectedTest);
String cassandraBuildFile =
rootPath.resolve("build.xml").toAbsolutePath().toString();
String antArgs = getAntArgs(context);
String antProps = getAntProps(context);

try {
for (Map.Entry<String, String> classTest
: classTestMap.entrySet()) {
String className = classTest.getKey();
String testStr = classTest.getValue();
String cmd = String.format(
"ant %s %s -buildfile %s testsome -Dtest.name=%s -Dtest.methods=%s",
antArgs,
antProps,
cassandraBuildFile,
className,
testStr);

Process process = Runtime.getRuntime().exec(cmd);

// redirect ant output to standard output
String line = null;
BufferedReader is = new BufferedReader(
new InputStreamReader(process.getInputStream()));

while ((line = is.readLine()) != null) {
System.out.println(line);
}

process.waitFor();
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}

@Override
public Pair<Map<String, String>, Map<String, String>> parseResult(
final Set<String> affectedTest) {
Map<String, String> successfulTest = new HashMap<>();
Map<String, String> failedTest = new HashMap<>();

for (String test : affectedTest) {
String[] cm = test.split("#");
String className = cm[0];
String methodName = cm[1];

String resultFileName = JUNIT_OUTPUT_XML
.replace("@", className)
.replace("#", methodName);
Path resultFilePath = Path.of(junitPath.toString(), resultFileName);
if (!resultFilePath.toFile().exists()) {
LOGGER.info(
"Cannot locate junit report file in: {}",
resultFilePath);
return new Pair<>(successfulTest, failedTest);
}
LOGGER.info("Reading result from {}", resultFilePath);

try {
// parse result xml file
DocumentBuilderFactory docFactory =
DocumentBuilderFactory.newInstance();

// completely disable DOCTYPE declaration
docFactory.setFeature(
"http://apache.org/xml/features/disallow-doctype-decl",
true);

DocumentBuilder db = docFactory.newDocumentBuilder();
Document doc = db.parse(resultFilePath.toFile());

// get testsuite node
Element testsuite = doc.getDocumentElement();

// get testcase nodes
NodeList testcases = testsuite.getElementsByTagName("testcase");
if (testcases.getLength() != 1) {
LOGGER.info("Cannot find result for test: {}", test);
continue;
}

// get individual testcase node
Node testcaseNode = testcases.item(0);
if (testcaseNode.getNodeType() == Node.ELEMENT_NODE) {
Element testcase = (Element) testcaseNode;
String executionDuration = testcase.getAttribute("time");
String testedMethodName = testcase.getAttribute("name");

// in the junit report file, some failed tests methods have
// different names than that of the original test methods.
// Typically, it results from modified parameters causing
// the test to fail before executing the test itself.
// The name displayed in surefire report could be any
// one of the following:
// 1. test class initialization method name
// 2. test class name
// 3. empty string
if (methodName.equals(testedMethodName)) {
// populate runningTimes map
successfulTest.put(
className + "#" + methodName,
executionDuration);
} else {
// populate errors map
NodeList errorNodes =
testcase.getElementsByTagName("error");
if (errorNodes.getLength() != 0) {
failedTest.put(
className + "#" + methodName,
errorNodes.item(0).getTextContent());
}
NodeList failureNodes =
testcase.getElementsByTagName("failure");
if (failureNodes.getLength() != 0) {
failedTest.put(
className + "#" + methodName,
failureNodes.item(0).getTextContent());
}
}
}
} catch (ParserConfigurationException
| SAXException
| IOException e) {
e.printStackTrace();
}
}

return new Pair<>(successfulTest, failedTest);
}

private Map<String, String> getClassTestMap(
final Set<String> affectedTest) {
// Cassandra uses ant to manage the project and ant allow
// running multiple tests within the same class at once.
// In here, we group those tests together.
Map<String, List<String>> associatedMethods = new HashMap<>();
affectedTest.forEach(test -> {
String[] cm = test.split("#");
String className = cm[0];
String methodName = cm[1];

associatedMethods.computeIfAbsent(
className,
k -> new ArrayList<>()).add(methodName);
});

// group tests by their class to resue test fixure
Map<String, String> testMap = new HashMap<>();
associatedMethods.forEach((className, methods) ->
testMap.put(className, String.join(",", methods))
);

return testMap;
}

private String getAntArgs(final Map<String, String> context) {
Set<String> antArgs = new HashSet<>();

if (!context.containsKey("args")) {
return "";
}

String[] args = context.get("args").split(",");
antArgs.addAll(Arrays.asList(args));
return String.join(" ", antArgs);
}

private String getAntProps(final Map<String, String> context) {
if (!context.containsKey("props")) {
return "";
}

String[] props = context.get("props").split(",");
StringBuilder sb = new StringBuilder();
sb.append("-D");
for (String prop : props) {
sb.append(prop);
sb.append(" ");
}

return sb.toString();
}
}