diff --git a/.vscode/launch.json b/.vscode/launch.json index fa9529bef36..789e4e10b33 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,14 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "type": "java", + "name": "BreakTest", + "request": "launch", + "mainClass": "org.rascalmpl.compiler.BreakTest", + "projectName": "rascal", + "console": "integratedTerminal" + }, { "type": "java", "name": "RascalCheck", diff --git a/src/org/rascalmpl/compiler/BreakTest.java b/src/org/rascalmpl/compiler/BreakTest.java new file mode 100644 index 00000000000..3610fb61b02 --- /dev/null +++ b/src/org/rascalmpl/compiler/BreakTest.java @@ -0,0 +1,117 @@ +package org.rascalmpl.compiler; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import org.rascalmpl.debug.IRascalMonitor; +import org.rascalmpl.shell.RascalShell; +import org.rascalmpl.shell.ShellEvaluatorFactory; +import org.rascalmpl.test.infrastructure.RascalJUnitTestRunner; +import org.rascalmpl.uri.URIResolverRegistry; + +import io.usethesource.vallang.ISourceLocation; + +public class BreakTest { + + private static final String BREAKING_MODULE = "lang::rascalcore::check::tests::ChangeScenarioTests"; + private static final String BREAKING_TEST = "fixedErrorsDisappear2"; + + private static final int PARALLEL_RUNS = 8; // set to 1 to avoid any multi-threading interactions, but it might take 20 rounds or something + private static final int TRIES = 1000 / PARALLEL_RUNS; + + public static void main(String[] args) throws IOException, InterruptedException { + RascalShell.setupJavaProcessForREPL(); + + var term = RascalShell.connectToTerminal(); + var monitor = IRascalMonitor.buildConsoleMonitor(term); + var error = monitor instanceof PrintWriter ? (PrintWriter) monitor : new PrintWriter(System.err, false); + try { + AtomicBoolean failed = new AtomicBoolean(false); + AtomicInteger done = new AtomicInteger(0); + for (int t = 0; t < PARALLEL_RUNS; t++) { + var name = "Thread " + (t + 1); + var tr = new Thread(() -> { + try { + if (crashTest(monitor, error, name, failed)) { + failed.set(true); + System.err.println("We got a failure, exiting now!"); + Thread.sleep(1000); + System.exit(1); + } + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + done.incrementAndGet(); + } + }); + tr.start(); + } + while (done.get() < PARALLEL_RUNS && !failed.get()) { + Thread.sleep(100); + } + } finally { + error.close(); + } + } + + static boolean crashTest(IRascalMonitor monitor, PrintWriter errorPrinter, String name, AtomicBoolean failed) { + var output = new StringWriter(); + var iFailed = new AtomicBoolean(false); + try (var err = new PrintWriter(output, true); var out= new PrintWriter(output, false)) { + var projectRoot = RascalJUnitTestRunner.inferProjectRootFromClass(BreakTest.class); + var evaluator = ShellEvaluatorFactory.getDefaultEvaluatorForLocation(projectRoot, Reader.nullReader(), err, out, monitor, "$test-"+name+"$"); + evaluator.getConfiguration().setErrors(true); + // make sure we're writing to the outputs + evaluator.overwritePrintWriter(out, err); + + evaluator.doImport(monitor, BREAKING_MODULE); + try { + monitor.job(name, TRIES, (jobname, step) -> { + try { + for (int i = 0; i < TRIES; i++) { + if (failed.get()) { + return false; + } + monitor.jobStep(jobname, "Running: try " + (i + 1)); + evaluator.call(BREAKING_TEST); + } + + } catch (Throwable e ) { + failed.set(true); + iFailed.set(true); + err.println("❌ test fail "); + err.println(e); + } + return null; + }); + } finally { + // clean up memory + var memoryModule = evaluator.getHeap().getModule("lang::rascalcore::check::TestShared"); + var testRoot = memoryModule.getFrameVariable("testRoot"); + try { + URIResolverRegistry.getInstance().remove((ISourceLocation)testRoot.getValue(), true); + } + catch (Throwable e) { + err.println("Failure to cleanup the cache"); + } + } + + } + + if (iFailed.get()) { + errorPrinter.println("❌❌❌ Test run failed: " + name); + errorPrinter.println("Job output:"); + errorPrinter.println(output.toString()); + return true; + } + return false; + } +} diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/ADTandGrammar.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/ADTandGrammar.rsc index e60eb823db1..1f2f3564282 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/ADTandGrammar.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/ADTandGrammar.rsc @@ -178,7 +178,7 @@ tuple[bool, TModel, ModuleStatus] addGrammar(MODID moduleId, set[MODID] imports, = getTModelForModule(m, ms); if(!found) { msg = error("Cannot add grammar or tmodel since `` is not found", ms.moduleLocs[moduleId] ? |unknown:///|); - println(msg); // TODO: Just to record this event; this should probably go to a log file + //println(msg); // TODO: Just to record this event; this should probably go to a log file ms.messages[moduleId] ? {} += { msg }; tm1 = tmodel(modelName=qualifiedModuleName, messages=[msg]); return ; diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/Checker.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/Checker.rsc index c3ade402c51..37a3e4e7c56 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/Checker.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/Checker.rsc @@ -553,7 +553,7 @@ bool uptodateTPls(list[loc] candidates, list[str] mnames, PathConfig pcfg){ for(int i <- index(candidates)){ mloc = candidates[i]; = getTPLReadLoc(mnames[i], pcfg); - if(!found || lastModified(mloc) > lastModified(tpl)){ + if(!found || lastModified(mloc) >= lastModified(tpl)){ return false; } } diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/CheckerCommon.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/CheckerCommon.rsc index 592e642e3ed..b8f89b62721 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/CheckerCommon.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/CheckerCommon.rsc @@ -225,7 +225,7 @@ bool tplOutdated(MODID moduleId, PathConfig pcfg){ lmMloc = lastModified(mloc); lmTpl = lastModified(tpl); res = !found || lmMloc > lmTpl; - //println("tplOutdated : ; mloc: \> tpl: : lmTpl>, (, )"); + // println("tplOutdated : ; mloc: \> tpl: : lmTpl>, (, )"); return res; } catch _: { return false; diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc index 220ba9e2d21..103f5c28c85 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/Import.rsc @@ -328,10 +328,11 @@ str getModuleNameFromAnyLogical(loc l){ tuple[bool, ModuleStatus] importsAndExtendsAreBinaryCompatible(TModel tm, set[MODID] importsAndExtends, ModuleStatus ms){ moduleName = tm.modelName; physical2logical = invertUnique(tm.logical2physical); - - modRequires = { lg | l <- range(tm.useDef), - physical2logical[l]?, lg := physical2logical[l], - moduleName !:= getModuleNameFromAnyLogical(lg) }; + modRequires = {lg | l <- range(tm.useDef), + l in physical2logical, + lg := physical2logical[l], + moduleName != getModuleNameFromAnyLogical(lg) + }; provided = {}; if(!isEmpty(modRequires)){ for(m <- importsAndExtends){ @@ -342,8 +343,6 @@ tuple[bool, ModuleStatus] importsAndExtendsAreBinaryCompatible(TModel tm, set[MO } } - //println(" requires "); - if(isEmpty(modRequires - provided)){ //println("importsAndExtendsAreBinaryCompatible : satisfied"); return ; diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/TestConfigs.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/TestConfigs.rsc index e350c8902fc..a4bc0080a82 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/TestConfigs.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/TestConfigs.rsc @@ -32,6 +32,7 @@ import lang::rascalcore::check::BasicRascalConfig; import lang::rascalcore::check::RascalConfig; import lang::rascalcore::check::ModuleLocations; +import lang::rascalcore::check::TestShared; // import lang::rascalcore::CompilerPathConfig; // Duplicate in lang::rascalcore::compile::util::Names, factor out @@ -74,10 +75,10 @@ public PathConfig getDefaultTestingPathConfig() { npc += 1; snpc = ""; return pathConfig( - srcs = [ |memory:///test-modules/|, |std:///| ], - bin = |memory:///test-modules/rascal-tests-bin-|, - generatedSources = |memory:///test-modules/generated-test-sources-|, - generatedResources = |memory:///test-modules/generated-test-resources-|, + srcs = [ testModulesRoot, |std:///| ], + bin = testRoot + "rascal-tests-bin-", + generatedSources = testRoot + "generated-test-sources-", + generatedResources = testRoot + "generated-test-resources-", libs = [ ] ); } @@ -92,10 +93,10 @@ public PathConfig getReleasedStandardLibraryTestingPathConfig() { npc += 1; snpc = ""; return pathConfig( - srcs = [ |memory:///test-modules/| ], - bin = |memory:///test-modules/rascal-tests-bin-|, - generatedSources = |memory:///test-modules/generated-test-sources-|, - generatedResources = |memory:///test-modules/generated-test-resources-|, + srcs = [ testModulesRoot ], + bin = testRoot + "rascal-tests-bin-", + generatedSources = testRoot + "generated-test-sources-", + generatedResources = testRoot + "generated-test-resources-", libs = [ |lib://rascal| ] ); } @@ -108,7 +109,7 @@ public PathConfig getRascalProjectTestingPathConfig() { snpc = ""; return pathConfig( srcs = [|project://rascal/src/org/rascalmpl/library|], - bin = |memory:///test-modules/rascal-lib-bin-|, + bin = testModulesRoot + "rascal-lib-bin-", libs = [] ); } diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/TestShared.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/TestShared.rsc new file mode 100644 index 00000000000..ac347670a50 --- /dev/null +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/TestShared.rsc @@ -0,0 +1,8 @@ +module lang::rascalcore::check::TestShared + +import util::UUID; + + +public loc testRoot = uuid()[scheme="memory"]; +public loc testModulesRoot = testRoot + "src"; + diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/BinaryDependencyTests.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/BinaryDependencyTests.rsc index 5bf5c69c708..11ba7cf8f51 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/BinaryDependencyTests.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/BinaryDependencyTests.rsc @@ -46,6 +46,8 @@ import String; import lang::rascalcore::check::ModuleLocations; import util::FileSystem; import util::SemVer; +import lang::rascalcore::check::TestShared; +import lang::rascalcore::check::tests::StaticTestingUtils; // ---- Utilities for test setup ---------------------------------------------- @@ -57,10 +59,10 @@ data PathConfig(loc generatedResources=|unknown:///|, loc generatedSources=|unkn data Project = project(str name, map[str moduleName, str moduleText] modules, PathConfig pcfg); -void clearMemory() { remove(|memory:///|, recursive = true); } +void clearMemory() { remove(testRoot, recursive = true); } loc projectDir(str pname) - = |memory:///|; + = testRoot + pname; loc src(str pname) = projectDir(pname) + "src/"; @@ -99,6 +101,9 @@ str writeModule(str mname, str mtext){ throw "Parse error in "; } +loc getModuleLoc(str mname, Project pd) + = src(pd.name) + ".rsc"; + PathConfig createPathConfig(str pname){ return pathConfig( srcs=[src(pname)], @@ -111,7 +116,9 @@ PathConfig createPathConfig(str pname){ Project addModule(str mname, str mtext, Project pd){ pd.modules[mname] = writeModule(mname, mtext); - writeFile(src(pd.name) + ".rsc", pd.modules[mname]); + mloc = getModuleLoc(mname, pd); + writeFile(mloc, pd.modules[mname]); + assert exists(mloc) : " does not exist after write"; return pd; } @@ -119,14 +126,18 @@ Project changeModule(str mname, str mtext, Project pd){ if(!pd.modules[mname]?) throw "Module does not exist in "; pd.modules[mname] = writeModule(mname, mtext); - writeFile(src(pd.name) + ".rsc", pd.modules[mname]); + mloc = getModuleLoc(mname, pd); + writeFile(mloc, pd.modules[mname]); + assert exists(mloc) : " does not exist after write"; return pd; } Project removeSourceOfModule(str mname, Project pd){ if(!pd.modules[mname]?) throw "Cannot remove non-existing module "; pd.modules = delete(pd.modules, mname); - remove(src(pd.name) + ".rsc", recursive=true); + mloc = getModuleLoc(mname, pd); + remove(mloc, recursive=true); + assert !exists(mloc): " not removed"; return pd; } @@ -533,7 +544,7 @@ test bool incompatibleVersionsOfBinaryLibrary(){ // Important: we do not recompile TP (and thus it will contain the outdated version of IO) - // Update Checks' modification time to make sure it will rechecked + // Update Checks' modification time to make sure it will be rechecked touch(getRascalModuleLocation("Check", core.pcfg)); // Recompile Check and discover the error return checkExpectErrors("Check", ["Review of dependencies, reconfiguration or recompilation needed: binary module `TP` depends (indirectly) on incompatible module(s)"], core.pcfg, remove = [rascal, typepal, core]); diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/ChangeScenarioTests.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/ChangeScenarioTests.rsc index 00c5b855b5b..afb277fa129 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/ChangeScenarioTests.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/ChangeScenarioTests.rsc @@ -469,6 +469,29 @@ test bool breakingChange1(){ return expectReChecks(D, ["C", "D"]); } +test bool fixedErrorsDisappear2() { // ht @toinehartman + clearMemory(); + pcfg = getDefaultTestingPathConfig(); + + mlocs = writeModules(" + module ParserBase + "); + + assert checkModulesOK(mlocs, pathConfig=pcfg) : "Precondition failed: no errors expected!"; + + // Introduce a type error (import of module that does not exist) + l = writeModule(" + module ParserBase + + import vis::ParseTree; // module does not exist -\> error + + "); + + assert missingModuleInModule(l, pathConfig=pcfg) : "Precondition failed: expected at least one error, but got none!"; + + return true; +} + test bool fixedErrorsDisappear() { // ht @toinehartman clearMemory(); pcfg = getReleasedStandardLibraryTestingPathConfig(); diff --git a/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/StaticTestingUtils.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/StaticTestingUtils.rsc index b64028e1f68..9430c36cb7d 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/StaticTestingUtils.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/StaticTestingUtils.rsc @@ -42,8 +42,11 @@ import Relation; import Set; import util::Reflective; import ParseTree; +import util::FileSystem; import lang::rascalcore::check::RascalConfig; +import lang::rascalcore::check::TestShared; + import lang::rascalcore::check::Checker; import lang::rascal::\syntax::Rascal; @@ -73,7 +76,7 @@ tuple[str,str] extractModuleNameAndBody(str moduleText){ loc composeModule(str stmts){ return writeModule( - "module TestModule + "module TestModule 'value main(){ ' \n ' return true; @@ -81,14 +84,14 @@ loc composeModule(str stmts){ } void clearMemory() { - remove(|memory:///test-modules/| recursive = true); + remove(testRoot, recursive = true); } str cleanName(str name) = name[0] == "\\" ? name[1..] : name; loc writeModule(str moduleText){ = extractModuleNameAndBody(moduleText); - mloc = |memory:///test-modules/.rsc|; + mloc = testModulesRoot + ".rsc"; writeFile(mloc, moduleText); return mloc; } @@ -99,10 +102,22 @@ list[loc] writeModules(str modules...) void removeModule(str mname){ pcfg = getDefaultTestingPathConfig(); name = cleanName(mname); - remove(|memory:///test-modules/.rsc|); + remove(testModulesRoot + ".rsc"); remove(pcfg.generatedResources + ".tpl"); } +void printModules(){ + println("\<\<\<\<"); + for(f <- find(testRoot, "rsc")){ + println(" : + '"); + } + for(f <- find(testRoot, "tpl")){ + println(": "); + } + println("\>\>\>\>"); +} + set[Message] getErrorMessages(ModuleStatus r) = { m | m <- getAllMessages(r), m is error };