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

Compile and overwrite test #175

Merged
merged 16 commits into from
Oct 15, 2021
Merged
Show file tree
Hide file tree
Changes from 7 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
2 changes: 1 addition & 1 deletion .github/workflows/maven.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,4 @@ jobs:
- name: Test Cli
run: |
cd jrd
mvn --batch-mode test -Dtest=CliTest -DfailIfNoTests=false
mvn --batch-mode test -Dtest=*CliTest -DfailIfNoTests=false
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why there is this condition at all? Test all!
Also the -DfailIfNoTests=false seems wrong. lets fail

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ook. thanx

22 changes: 13 additions & 9 deletions runtime-decompiler/src/main/java/org/jrd/backend/data/Cli.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,25 @@ public class Cli {
protected static final String HELP = "-help";
protected static final String H = "-h";

protected static final String R = "-r";
protected static final String P = "-p";
protected static final String CP = "-cp";

private final List<String> filteredArgs;
private final VmManager vmManager;
private final PluginManager pluginManager;
private Saving saving;
private boolean isVerbose;

protected static class Saving implements CommonUtils.StatusKeeper {
protected static final String DEFAULT = "default";
protected static final String EXACT = "exact";
protected static final String FQN = "fqn";
protected static final String DIR = "dir";
static class Saving implements CommonUtils.StatusKeeper {
static final String DEFAULT = "default";
static final String EXACT = "exact";
static final String FQN = "fqn";
static final String DIR = "dir";
private final String as;
private final String like;

public Saving(String as, String like) {
Saving(String as, String like) {
this.as = as;
if (like == null) {
this.like = DEFAULT;
Expand Down Expand Up @@ -299,13 +303,13 @@ private final class CompileArguments {
for (int i = 1; i < filteredArgs.size(); i++) {
String arg = filteredArgs.get(i);

if ("-p".equals(arg)) {
if (P.equals(arg)) {
wantedCustomCompiler = filteredArgs.get(i + 1);
i++; // shift
} else if ("-cp".equals(arg)) {
} else if (CP.equals(arg)) {
puc = filteredArgs.get(i + 1);
i++; // shift
} else if ("-r".equals(arg)) {
} else if (R.equals(arg)) {
isRecursive = true;
} else {
File fileToCompile = new File(arg);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public final class Help {
static final String LIST_JVMS_FORMAT = LIST_JVMS;
static final String LIST_PLUGINS_FORMAT = LIST_PLUGINS;
static final String LIST_CLASSES_FORMAT = LIST_CLASSES + " <PUC> [<CLASS REGEX>...]";
static final String COMPILE_FORMAT = COMPILE + " [-p <PLUGIN>] [-cp <PUC>] [-r] <PATH>...";
static final String COMPILE_FORMAT = COMPILE + " [" + P + " <PLUGIN>] [" + CP + " <PUC>] [" + R + "] <PATH>...";
static final String DECOMPILE_FORMAT = DECOMPILE + " <PUC> <PLUGIN> <CLASS REGEX>...";
static final String OVERWRITE_FORMAT = OVERWRITE + " <PUC> <FQN> [<CLASS FILE>]";
static final String INIT_FORMAT = INIT + " <PUC> <FQN>";
Expand All @@ -40,9 +40,9 @@ public final class Help {
private static final String LIST_CLASSES_TEXT = "List all loaded classes of a process, optionally filtering them.\n" +
"Only '" + SAVE_LIKE + " " + Saving.EXACT + "' or '" + SAVE_LIKE + " " + Saving.DEFAULT +
"' are allowed as saving modifiers.";
private static final String COMPILE_TEXT = "Compile local files against runtime classpath, specified by -cp.\n" +
"Use -p to utilize some plugins' (like jasm or jcoder) bundled compilers.\n" +
"Use -r for recursive search if <PATH> is a directory.\n" +
private static final String COMPILE_TEXT = "Compile local files against runtime classpath, specified by " + CP + ".\n" +
"Use " + P + " to utilize some plugins' (like jasm or jcoder) bundled compilers.\n" +
"Use " + R + " for recursive search if <PATH> is a directory.\n" +
"If the argument of '" + SAVE_AS + "' is a valid PID or URL, " +
"the compiled code will be attempted to be injected into that process.\n" +
"If multiple PATHs were specified, but no '" + SAVE_AS + "', the process fails.";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
package org.jrd.backend.data;

import org.jrd.backend.core.AgentRequestAction;
import org.jrd.frontend.frame.main.DecompilationController;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Timeout;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public abstract class AbstractAgentNeedingTest {

protected Model model;
protected AbstractSourceTestClass dummy;
protected final JunitStderrOutThief streams = new JunitStderrOutThief();

abstract AbstractSourceTestClass dummyProvider() throws AbstractSourceTestClass.SourceTestClassWrapperException;

@BeforeAll
static void startup() {
String agentPath = Config.getConfig().getAgentExpandedPath();

Assumptions.assumeTrue(
!agentPath.isEmpty(),
"Agent path is not set up, aborting CliTest."
);
Assumptions.assumeTrue(
new File(agentPath).exists(),
"Agent path is set up to nonexistent file, aborting CliTest."
);
}

@Timeout(5)
@BeforeEach
void setup() throws InterruptedException {
try {
dummy = dummyProvider();
} catch (AbstractSourceTestClass.SourceTestClassWrapperException e) {
Assertions.fail(e);
}
Assertions.assertTrue(dummy.isAlive());

model = new Model(); // must be below dummy process execution to be aware of it during VmManager instantiation
while (model.getVmManager().findVmFromPidNoException(dummy.getPid()) == null) {
Thread.sleep(100);
model.getVmManager().updateLocalVMs();
}

streams.captureStreams(true);
}

@AfterEach
void cleanup() {
streams.captureStreams(false);

Assertions.assertTrue(dummy.isAlive());
// halt agent, otherwise an open socket prevents termination of dummy process
AgentRequestAction request = DecompilationController.createRequest(
model.getVmManager().findVmFromPid(dummy.getPid()), AgentRequestAction.RequestAction.HALT, ""
);
String response = DecompilationController.submitRequest(model.getVmManager(), request);
Assertions.assertEquals("ok", response);

Assertions.assertTrue(dummy.isAlive());
dummy.terminate();
}

private static boolean isDifferenceTolerable(double samenessPercentage, int actualChanges, int totalSize) {
assert samenessPercentage >= 0 && samenessPercentage <= 1.0;

double changesAllowed = (1.0 - samenessPercentage) * totalSize;
return actualChanges <= changesAllowed;
}

static void assertEqualsWithTolerance(String s1, String s2, double samenessPercentage) {
Assertions.assertTrue(isDifferenceTolerable(
samenessPercentage,
LevenshteinDistance.calculate(s1, s2),
Math.max(s1.length(), s2.length())
));
}

static void assertEqualsWithTolerance(List<String> l1, List<String> l2, double samenessPercentage) {
// symmetric difference
Set<String> intersection = new HashSet<>(l1);
intersection.retainAll(l2);

Set<String> difference = new HashSet<>();
difference.addAll(l1);
difference.addAll(l2);
difference.removeAll(intersection);

Assertions.assertTrue(isDifferenceTolerable(samenessPercentage, difference.size(), Math.max(l1.size(), l2.size())));
}

public static final class LevenshteinDistance {
/**
* Calculates the Levenshtein distance between two strings.<br/>
* Uses a 2D array to represent individual changes, therefore the time complexity is quadratic
* (in reference to the strings' length).
*
* @param str1 the first string
* @param str2 the second string
* @return an integer representing the amount of atomic changes between {@code str1} and {@code str2}
*/
public static int calculate(String str1, String str2) {
int[][] matrix = new int[str1.length() + 1][str2.length() + 1];

for (int i = 0; i <= str1.length(); i++) {
for (int j = 0; j <= str2.length(); j++) {
if (i == 0) { // distance between "" and str2 == how long str2 is
matrix[i][j] = j;
} else if (j == 0) { // distance between str1 and "" == how long str1 is
matrix[i][j] = i;
} else {
int substitution = matrix[i - 1][j - 1] +
substitutionCost(str1.charAt(i - 1), str2.charAt(j - 1));
int insertion = matrix[i][j - 1] + 1;
int deletion = matrix[i - 1][j] + 1;

matrix[i][j] = min3(substitution, insertion, deletion);
}
}
}

return matrix[str1.length()][str2.length()]; // result is in the bottom-right corner
}

private static int substitutionCost(char a, char b) {
return (a == b) ? 0 : 1;
}

private static int min3(int a, int b, int c) {
return Math.min(a, Math.min(b, c));
}
}


public static class JunitStderrOutThief {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand this renaming.

private final ByteArrayOutputStream out;
private final ByteArrayOutputStream err;

private final PrintStream originalOut;
private final PrintStream originalErr;

JunitStderrOutThief() {
out = new ByteArrayOutputStream();
err = new ByteArrayOutputStream();
originalOut = System.out;
originalErr = System.err;
}

public void captureStreams(boolean capture) {
if (capture) {
out.reset();
err.reset();
}

System.setOut(capture ? new PrintStream(out, true, StandardCharsets.UTF_8) : originalOut);
System.setErr(capture ? new PrintStream(err, true, StandardCharsets.UTF_8) : originalErr);
}

private String get(ByteArrayOutputStream which) {
String string = which.toString(StandardCharsets.UTF_8);
which.reset();

return string;
}

public String getOut() {
return get(out);
}

public byte[] getOutBytes() {
byte[] bytes = out.toByteArray();
out.reset();

return bytes;
}

public String getErr() {
return get(err);
}
}


public static String readBinaryAsString(File f) throws IOException {
try (FileInputStream fis = new FileInputStream(f)) {
return readBinaryAsString(fis, "UTF-8", CodingErrorAction.REPLACE);
}
}

public static String readBinaryAsString(FileInputStream input, String charBase, CodingErrorAction action) throws IOException {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused parameter action.

CharsetDecoder decoder = Charset.forName(charBase).newDecoder();
decoder.onMalformedInput(CodingErrorAction.IGNORE);
InputStreamReader reader = new InputStreamReader(input, decoder);
StringBuilder sb = new StringBuilder();
while (true) {
int i = reader.read();
if (i < 0) {
break;
}
sb.append((char) i);
}
reader.close();
return sb.toString();
}
}
Loading