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

Added tests around IO::watch and IO::unwatch #1659

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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
24 changes: 24 additions & 0 deletions src/org/rascalmpl/library/Prelude.java
Original file line number Diff line number Diff line change
Expand Up @@ -3784,6 +3784,7 @@ public void sleep(IInteger seconds) {
private static final class ReleasableCallback implements Consumer<ISourceLocationChanged> {
private final WeakReference<IFunction> target;
private final ISourceLocation src;
private final ISourceLocation srcResolved;
private final boolean recursive;
private final int hash;

Expand All @@ -3792,15 +3793,38 @@ private static final class ReleasableCallback implements Consumer<ISourceLocatio

public ReleasableCallback(ISourceLocation src, boolean recursive, IFunction target, IValueFactory values, TypeStore store) {
this.src = src;
this.srcResolved = safeResolve(src);
this.recursive = recursive;
this.target = new WeakReference<>(target);
this.hash = src.hashCode() + 7 * target.hashCode();
this.values = values;
this.store = store;
}

private static ISourceLocation safeResolve(ISourceLocation src) {
try {
var result = URIResolverRegistry.getInstance().logicalToPhysical(src);
if (result != null) {
return result;
}
return src;
} catch (IOException e) {
return src;
}
}

private boolean exactMatch(ISourceLocation loc) {
return loc.equals(src) || (srcResolved != src && loc.equals(srcResolved));
}

@Override
public void accept(ISourceLocationChanged e) {
if (!recursive && !exactMatch(e.getLocation())) {
// if we are not recursive, and changes come in for something that's not what we requested
// for example due to the backend only supporting directory level watches
// we just ignore it
return;
}
IFunction callback = target.get();
if (callback == null) {
try {
Expand Down
5 changes: 5 additions & 0 deletions src/org/rascalmpl/test/infrastructure/RecursiveTestSuite.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import org.junit.runners.Suite;
import org.junit.runners.model.InitializationError;

import junit.framework.TestCase;

public class RecursiveTestSuite extends Suite {

public RecursiveTestSuite(Class<?> setupClass)
Expand Down Expand Up @@ -68,6 +70,9 @@ else if (f.getName().endsWith(".class")) {
result.add(currentClass);
}
}
else if (TestCase.class.isAssignableFrom(currentClass)) {
result.add(currentClass);
}
else {
for (Method m: currentClass.getMethods()) {
if (m.isAnnotationPresent(Test.class)) {
Expand Down
108 changes: 106 additions & 2 deletions test/org/rascalmpl/test/functionality/IOTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,23 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;

import org.rascalmpl.interpreter.Evaluator;
import org.rascalmpl.interpreter.IEvaluator;
import org.rascalmpl.interpreter.env.GlobalEnvironment;
import org.rascalmpl.interpreter.env.ModuleEnvironment;
import org.rascalmpl.interpreter.load.StandardLibraryContributor;
import org.rascalmpl.interpreter.result.Result;
import org.rascalmpl.uri.URIUtil;
import org.rascalmpl.values.ValueFactoryFactory;

import io.usethesource.vallang.IBool;
import io.usethesource.vallang.IValue;
import io.usethesource.vallang.IValueFactory;
import io.usethesource.vallang.exceptions.FactTypeUseException;
import io.usethesource.vallang.io.ATermReader;
import io.usethesource.vallang.type.Type;
import io.usethesource.vallang.type.TypeFactory;
import io.usethesource.vallang.type.TypeStore;
import org.rascalmpl.values.ValueFactoryFactory;

import junit.framework.TestCase;

public class IOTests extends TestCase {
Expand Down Expand Up @@ -105,5 +113,101 @@ public void testATermReader() {
}
}

private final IEvaluator<Result<IValue>> setupWatchEvaluator() {
return setupWatchEvaluator(false);
}
private final IEvaluator<Result<IValue>> setupWatchEvaluator(boolean debug) {
var heap = new GlobalEnvironment();
var root = heap.addModule(new ModuleEnvironment("___test___", heap));
var evaluator = new Evaluator(ValueFactoryFactory.getValueFactory(), System.in, System.err, System.out, root, heap);

evaluator.addRascalSearchPathContributor(StandardLibraryContributor.getInstance());

evaluator.addRascalSearchPath(URIUtil.rootLocation("test-modules"));
evaluator.addRascalSearchPath(URIUtil.rootLocation("benchmarks"));
executeCommand(evaluator, "import IO;");
executeCommand(evaluator, "int trig = 0;");
executeCommand(evaluator, "void triggerWatch(LocationChangeEvent tp) { trig = trig + 1; " + (debug? "println(tp);": "") + " }");
return evaluator;
}

private static IValue executeCommand(IEvaluator<Result<IValue>> eval, String command) {
var result = eval.eval(null, command, URIUtil.rootLocation("stdin"));
if (result.getStaticType().isBottom()) {
return null;
}
return result.getValue();
}

private static boolean executeBooleanExpression(IEvaluator<Result<IValue>> eval, String expr) {
var result = executeCommand(eval, expr);
if (result instanceof IBool) {
return ((IBool)result).getValue();
}
return false;
}


public void testWatch() throws InterruptedException {
var evalTest = setupWatchEvaluator();
executeCommand(evalTest, "writeFile(|tmp:///a/make-dir.txt|, \"hi\");");
executeCommand(evalTest, "watch(|tmp:///a/|, true, triggerWatch);");
executeCommand(evalTest, "writeFile(|tmp:///a/test-watch.txt|, \"hi\");");
Thread.sleep(100); // give it some time to trigger the watch callback

assertTrue("Watch should have been triggered", executeBooleanExpression(evalTest, "trig > 0"));
}

public void testWatchNonRecursive() throws InterruptedException {
var evalTest = setupWatchEvaluator();
executeCommand(evalTest, "watch(|tmp:///a/test-watch.txt|, false, triggerWatch);");
executeCommand(evalTest, "writeFile(|tmp:///a/test-watch.txt|, \"hi\");");
Thread.sleep(100); // give it some time to trigger the watch callback
assertTrue("Watch should have been triggered", executeBooleanExpression(evalTest, "trig > 0"));
}

public void testWatchDelete() throws InterruptedException {
var evalTest = setupWatchEvaluator();
executeCommand(evalTest, "writeFile(|tmp:///a/test-watch.txt|, \"hi\");");
executeCommand(evalTest, "watch(|tmp:///a/|, true, triggerWatch);");
executeCommand(evalTest, "remove(|tmp:///a/test-watch.txt|);");
Thread.sleep(100); // give it some time to trigger the watch callback
assertTrue("Watch should have been triggered for delete", executeBooleanExpression(evalTest, "trig > 0"));
}


public void testWatchSingleFile() throws InterruptedException {
var evalTest = setupWatchEvaluator();
executeCommand(evalTest, "writeFile(|tmp:///a/test-watch-a.txt|, \"making it exist\");");
executeCommand(evalTest, "watch(|tmp:///a/test-watch-a.txt|, false, triggerWatch);");
executeCommand(evalTest, "writeFile(|tmp:///a/test-watch.txt|, \"bye\");");
executeCommand(evalTest, "remove(|tmp:///a/test-watch.txt|);");
Thread.sleep(100); // give it some time to trigger the watch callback
assertTrue("Watch should not have triggered anything", executeBooleanExpression(evalTest, "trig == 0"));
}

public void testUnwatchStopsEvents() throws InterruptedException {
var evalTest = setupWatchEvaluator();
executeCommand(evalTest, "watch(|tmp:///a/|, true, triggerWatch);");
Thread.sleep(10);
executeCommand(evalTest, "unwatch(|tmp:///a/|, true, triggerWatch);");
Thread.sleep(100); // give it some time to trigger the watch callback
executeCommand(evalTest, "writeFile(|tmp:///a/test-watch.txt|, \"hi\");");
executeCommand(evalTest, "remove(|tmp:///a/test-watch.txt|);");
Thread.sleep(100); // give it some time to trigger the watch callback
assertTrue("Watch should not have triggered anything", executeBooleanExpression(evalTest, "trig == 0"));
}

public void testUnwatchStopsEventsUnrecursive() throws InterruptedException {
var evalTest = setupWatchEvaluator();
executeCommand(evalTest, "writeFile(|tmp:///a/test-watch.txt|, \"hi\");");
executeCommand(evalTest, "watch(|tmp:///a/test-watch.txt|, false, triggerWatch);");
Thread.sleep(10);
executeCommand(evalTest, "unwatch(|tmp:///a/test-watch.txt|, false, triggerWatch);");
Thread.sleep(100); // give it some time to trigger the watch callback
executeCommand(evalTest, "writeFile(|tmp:///a/test-watch.txt|, \"hi\");");
executeCommand(evalTest, "remove(|tmp:///a/test-watch.txt|);");
Thread.sleep(100); // give it some time to trigger the watch callback
assertTrue("Watch should not have triggered anything", executeBooleanExpression(evalTest, "trig == 0"));
}
}