From 4ea9288fda0a5cc7904d0febf9479564064f39dc Mon Sep 17 00:00:00 2001 From: Davy Landman Date: Thu, 29 Sep 2022 14:00:50 +0200 Subject: [PATCH 1/4] Added specific test to verify watches are working as expected --- .../rascalmpl/test/functionality/IOTests.java | 108 +++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/test/org/rascalmpl/test/functionality/IOTests.java b/test/org/rascalmpl/test/functionality/IOTests.java index ebef897f48..0de9cee3f9 100644 --- a/test/org/rascalmpl/test/functionality/IOTests.java +++ b/test/org/rascalmpl/test/functionality/IOTests.java @@ -17,6 +17,16 @@ 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; @@ -24,8 +34,6 @@ 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 { @@ -105,5 +113,101 @@ public void testATermReader() { } } + private final IEvaluator> setupWatchEvaluator() { + return setupWatchEvaluator(false); + } + private final IEvaluator> 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> 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> 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(true); + 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(false); + 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(false); + 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")); + } } From 935a4e9bbe6dceca9adde00e69d8df33303c87a5 Mon Sep 17 00:00:00 2001 From: Davy Landman Date: Thu, 29 Sep 2022 14:15:07 +0200 Subject: [PATCH 2/4] Fixed watch not respecting specific file names --- src/org/rascalmpl/library/Prelude.java | 24 +++++++++++++++++++ .../rascalmpl/test/functionality/IOTests.java | 8 +++---- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/org/rascalmpl/library/Prelude.java b/src/org/rascalmpl/library/Prelude.java index e80d20becb..1430c53b8e 100644 --- a/src/org/rascalmpl/library/Prelude.java +++ b/src/org/rascalmpl/library/Prelude.java @@ -3800,6 +3800,7 @@ public void sleep(IInteger seconds) { private static final class ReleasableCallback implements Consumer { private final WeakReference target; private final ISourceLocation src; + private final ISourceLocation srcResolved; private final boolean recursive; private final int hash; @@ -3808,6 +3809,7 @@ private static final class ReleasableCallback implements Consumer(target); this.hash = src.hashCode() + 7 * target.hashCode(); @@ -3815,8 +3817,30 @@ public ReleasableCallback(ISourceLocation src, boolean recursive, IFunction targ 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 { diff --git a/test/org/rascalmpl/test/functionality/IOTests.java b/test/org/rascalmpl/test/functionality/IOTests.java index 0de9cee3f9..5fe8bff1fd 100644 --- a/test/org/rascalmpl/test/functionality/IOTests.java +++ b/test/org/rascalmpl/test/functionality/IOTests.java @@ -159,7 +159,7 @@ public void testWatch() throws InterruptedException { } public void testWatchNonRecursive() throws InterruptedException { - var evalTest = setupWatchEvaluator(); + var evalTest = setupWatchEvaluator(true); 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 @@ -177,7 +177,7 @@ public void testWatchDelete() throws InterruptedException { public void testWatchSingleFile() throws InterruptedException { - var evalTest = setupWatchEvaluator(true); + 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\");"); @@ -187,7 +187,7 @@ public void testWatchSingleFile() throws InterruptedException { } public void testUnwatchStopsEvents() throws InterruptedException { - var evalTest = setupWatchEvaluator(false); + var evalTest = setupWatchEvaluator(); executeCommand(evalTest, "watch(|tmp:///a/|, true, triggerWatch);"); Thread.sleep(10); executeCommand(evalTest, "unwatch(|tmp:///a/|, true, triggerWatch);"); @@ -199,7 +199,7 @@ public void testUnwatchStopsEvents() throws InterruptedException { } public void testUnwatchStopsEventsUnrecursive() throws InterruptedException { - var evalTest = setupWatchEvaluator(false); + 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); From 400895260afbc0dcb107ee1931cee7c7d9280df7 Mon Sep 17 00:00:00 2001 From: Davy Landman Date: Fri, 30 Sep 2022 09:17:13 +0200 Subject: [PATCH 3/4] Fixed detection of regular java test classes --- .../rascalmpl/test/infrastructure/RecursiveTestSuite.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/org/rascalmpl/test/infrastructure/RecursiveTestSuite.java b/src/org/rascalmpl/test/infrastructure/RecursiveTestSuite.java index 63b3d65b5c..712e24a966 100644 --- a/src/org/rascalmpl/test/infrastructure/RecursiveTestSuite.java +++ b/src/org/rascalmpl/test/infrastructure/RecursiveTestSuite.java @@ -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) @@ -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)) { From a0926a461f853071957defaac4fa5d072facb73b Mon Sep 17 00:00:00 2001 From: Davy Landman Date: Fri, 30 Sep 2022 09:17:44 +0200 Subject: [PATCH 4/4] Disabled debug print --- test/org/rascalmpl/test/functionality/IOTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/org/rascalmpl/test/functionality/IOTests.java b/test/org/rascalmpl/test/functionality/IOTests.java index 5fe8bff1fd..c7840fc8a5 100644 --- a/test/org/rascalmpl/test/functionality/IOTests.java +++ b/test/org/rascalmpl/test/functionality/IOTests.java @@ -159,7 +159,7 @@ public void testWatch() throws InterruptedException { } public void testWatchNonRecursive() throws InterruptedException { - var evalTest = setupWatchEvaluator(true); + 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