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..112413ee547 --- /dev/null +++ b/src/org/rascalmpl/compiler/BreakTest.java @@ -0,0 +1,235 @@ +package org.rascalmpl.compiler; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Collections; +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.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import org.rascalmpl.debug.IRascalMonitor; +import org.rascalmpl.interpreter.Evaluator; +import org.rascalmpl.interpreter.result.AbstractFunction; +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.IBool; +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 String BREAKING_MODULE = "lang::rascalcore::check::tests::FunctionTCTests"; + private static final String BREAKING_TEST = "BoundOK1"; + + // notCompatibleAfterChangingFunctionArgument + // notCompatibleAfterChangingFunctionArgument + + private static final int PARALLEL_RUNS = 1; // 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); + AtomicBoolean failed = new AtomicBoolean(false); + try { + AtomicInteger done = new AtomicInteger(0); + for (int t = 0; t < PARALLEL_RUNS; t++) { + var name = "Thread " + (t + 1); + var tr = new Thread(() -> { + try { + if (crashTestFreshEval(monitor, error, name, failed)) { + failed.set(true); + error.flush(); + System.err.println("We got a failure, exiting now!"); + Thread.sleep(1000); + } + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } finally { + done.incrementAndGet(); + } + }); + tr.start(); + } + while (done.get() < PARALLEL_RUNS && !failed.get()) { + Thread.sleep(100); + } + } finally { + error.flush(); + error.close(); + } + if (failed.get()) { + System.out.flush(); + System.err.flush(); + System.exit(1); + } + } + + 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) -> { + String currentTest = ""; + var tests = evaluator.getHeap().getModule(BREAKING_MODULE).getTests(); + try { + tests = tests.stream() + .filter(t -> !t.hasTag("ignore")) + .collect(Collectors.toList()); + + monitor.jobTodo(jobname, tests.size() * TRIES); + + for (int i = 0; i < TRIES; i++) { + if (failed.get()) { + return false; + } + + Collections.shuffle(tests); + + monitor.jobStep(jobname, "Running: try " + (i + 1)); + for (var t: tests) { + currentTest = t.getName(); + monitor.jobStep(jobname, "Running: try " + (i + 1) + " => " + currentTest); + var result = t.call(); + if (!((IBool)result).getValue()) { + throw new RuntimeException("Test " + currentTest +" returned false"); + } + } + } + + } catch (Throwable e ) { + err.println("❌ test fail :" + currentTest); + err.println(e); + e.printStackTrace(err); + iFailed.set(true); + } + return null; + }); + } finally { + // clean up memory + var memoryModule = evaluator.getHeap().getModule("lang::rascalcore::check::TestConfigs"); + if (memoryModule != null ) { + 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()); + failed.set(true); + return true; + } + return false; + } + + static boolean crashTestFreshEval(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); + monitor.job(name, TRIES * 2, (jobname, step) -> { + String currentTest = ""; + var tests = Collections.emptyList(); + try { + for (int i = 0; i < TRIES; i++) { + if (failed.get()) { + return false; + } + monitor.jobStep(jobname, "Try " + (i + 1)); + var evaluator = ShellEvaluatorFactory.getDefaultEvaluatorForLocation(projectRoot, Reader.nullReader(), err, out, monitor, "$test-"+name+"$"); + try { + + evaluator.getConfiguration().setErrors(true); + // make sure we're writing to the outputs + evaluator.overwritePrintWriter(out, err); + + evaluator.doImport(monitor, BREAKING_MODULE); + + tests = evaluator.getHeap().getModule(BREAKING_MODULE).getTests() + .stream() + .filter(t -> !t.hasTag("ignore")) + .collect(Collectors.toList()); + + if (i == 0) { + monitor.jobTodo(jobname, tests.size() * TRIES); + } + + Collections.shuffle(tests); + + monitor.jobStep(jobname, "Running: try " + (i + 1)); + for (var t: tests) { + currentTest = t.getName(); + monitor.jobStep(jobname, "Running: try " + (i + 1) + " => " + currentTest); + var result = t.call(); + if (!((IBool)result).getValue()) { + throw new RuntimeException("Test " + currentTest +" returned false"); + } + } + } finally { + // clean up memory + var memoryModule = evaluator.getHeap().getModule("lang::rascalcore::check::TestConfigs"); + if (memoryModule != null) { + var testRoot = memoryModule.getFrameVariable("testRoot"); + try { + URIResolverRegistry.getInstance().remove((ISourceLocation)testRoot.getValue(), true); + } + catch (Throwable e) { + err.println("Failure to cleanup the cache"); + } + } + } + } + + } catch (Throwable e ) { + iFailed.set(true); + err.println("tests: " + Arrays.deepToString(tests.stream().map(AbstractFunction::getName).toArray())); + err.println("❌ test fail :" + currentTest); + err.println(e); + e.printStackTrace(err); + } + return null; + }); + } + + if (iFailed.get()) { + errorPrinter.println("❌❌❌ Test run failed: " + name); + errorPrinter.println("Job output:"); + errorPrinter.println(output.toString()); + errorPrinter.flush(); + failed.set(true); + 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 8a95b397a50..d3eeda94e39 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/Checker.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/Checker.rsc @@ -552,7 +552,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 55fadfad48e..2cbb5ab0c8b 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/TestConfigs.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/TestConfigs.rsc @@ -34,6 +34,10 @@ import lang::rascalcore::check::RascalConfig; import lang::rascalcore::check::ModuleLocations; // import lang::rascalcore::CompilerPathConfig; +import util::UUID; + + + // Duplicate in lang::rascalcore::compile::util::Names, factor out data PathConfig( loc generatedSources=|unknown:///|, @@ -64,6 +68,9 @@ loc TMP_COMPILED_RASCAL = |tmp:///compiled-rascal/|; // ---- PathConfigs for testing purposes -------------------------------------- +public loc testRoot = uuid()[scheme="memory"]; +public loc testModulesRoot = testRoot + "src"; + private int npc = 0; @synopsis{PathConfig for testing generated modules in |memory://test-modules/| in memory file system, not depending on any outside libraries.} @description{ @@ -74,10 +81,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 +99,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 +115,7 @@ public PathConfig getRascalProjectTestingPathConfig() { snpc = ""; return pathConfig( srcs = [|project://rascal/src/org/rascalmpl/library|], - bin = |memory:///test-modules/rascal-lib-bin-|, + bin = testRoot + "rascal-lib-bin-", libs = [] ); } 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..ecb76b91ded 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,7 @@ import String; import lang::rascalcore::check::ModuleLocations; import util::FileSystem; import util::SemVer; +import lang::rascalcore::check::tests::StaticTestingUtils; // ---- Utilities for test setup ---------------------------------------------- @@ -57,10 +58,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 +100,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 +115,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 +125,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 +543,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..7b6c16459f6 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,43 @@ test bool breakingChange1(){ return expectReChecks(D, ["C", "D"]); } +test bool timestampsIncrease() { + datetime lastTime = $2020-01-01T00:00:00.000+00:00$; + loc testLoc = |memory:///test-random-write-file|; + for (i <- [0..10000]) { + writeFile(testLoc, "Hoi"); + datetime newTime = lastModified(testLoc); + if (lastTime == newTime) { + println("Time did not change: == "); + return false; + } + } + return true; +} + +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/PackagerTests.rsc b/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/PackagerTests.rsc index 62902a84590..ee153433ed7 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/PackagerTests.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/PackagerTests.rsc @@ -48,7 +48,7 @@ private void runChecker(PathConfig pcfg, list[str] mnames) { test bool packagerRewritesTModelsCorrectly () { println("**** Checking List"); - tplRoot = |memory:///|; + tplRoot = |memory://packager-test/|; originalBin = tplRoot + "rascal-lib"; rewrittenBin = tplRoot + "rascal-lib-rewritten"; rascalLibPcfg = 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 85c1d4e8a83..214ed2150a3 100644 --- a/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/StaticTestingUtils.rsc +++ b/src/org/rascalmpl/compiler/lang/rascalcore/check/tests/StaticTestingUtils.rsc @@ -42,6 +42,7 @@ import Relation; import Set; import util::Reflective; import ParseTree; +import util::FileSystem; import lang::rascalcore::check::RascalConfig; import lang::rascalcore::check::Checker; @@ -74,7 +75,7 @@ tuple[str,str] extractModuleNameAndBody(str moduleText){ loc composeModule(str stmts){ return writeModule( - "module TestModule + "module TestModule 'value main(){ ' \n ' return true; @@ -82,14 +83,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; } @@ -100,10 +101,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 }; diff --git a/src/org/rascalmpl/uri/libraries/MemoryResolver.java b/src/org/rascalmpl/uri/libraries/MemoryResolver.java index c174c9ae370..3f14739f3e5 100644 --- a/src/org/rascalmpl/uri/libraries/MemoryResolver.java +++ b/src/org/rascalmpl/uri/libraries/MemoryResolver.java @@ -25,6 +25,8 @@ import java.nio.file.NoSuchFileException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; + import org.checkerframework.checker.nullness.qual.Nullable; import org.rascalmpl.uri.FileAttributes; import org.rascalmpl.uri.ISourceLocationInputOutput; @@ -62,8 +64,29 @@ */ public class MemoryResolver implements ISourceLocationInputOutput { + private static class MemoryEntry extends FSEntry { + /** keep track of the timestamp across entries */ + private static final AtomicLong now = new AtomicLong(System.currentTimeMillis()); + + /** + * Every time we're called, we get a new timestamp, to make sure file written at roughly the same + * time don't get the same modification timestamp + * @return + */ + private static long timestamp() { + return now.accumulateAndGet(System.currentTimeMillis(), (now, prev) -> { + if (now > prev) { + return now; + } + if (now == prev) { + return now + 1; + } + // else : prev > now + return prev + 1; + }); + } private final @Nullable byte[] contents; public MemoryEntry() { @@ -71,7 +94,11 @@ public MemoryEntry() { } public MemoryEntry(@Nullable byte[] contents) { - this(System.currentTimeMillis(), System.currentTimeMillis(), contents); + this(timestamp(), contents); + } + + private MemoryEntry(long created, @Nullable byte[] contents) { + this(created, created, contents); } public MemoryEntry(long created, long lastModified) { @@ -83,19 +110,11 @@ public MemoryEntry(long created, long lastModified, @Nullable byte[] contents) { } public MemoryEntry newContents(byte[] newContents) { - long newTimestamp = System.currentTimeMillis(); - if (newTimestamp <= getLastModified()) { - newTimestamp = getLastModified() + 1; - } - return new MemoryEntry(getCreated(), newTimestamp, newContents); + return new MemoryEntry(getCreated(), timestamp(), newContents); } public MemoryEntry copy() { - long newTimestamp = System.currentTimeMillis(); - if (newTimestamp <= getLastModified()) { - newTimestamp = getLastModified() + 1; - } - return new MemoryEntry(newTimestamp, newTimestamp, contents); + return new MemoryEntry(timestamp(), contents); } } diff --git a/test/org/rascalmpl/compiler/lang/rascalcore/check/tests/RunTests.java b/test/org/rascalmpl/compiler/lang/rascalcore/check/tests/RunTests.java index 05cf4b7b415..26e6a6591bf 100644 --- a/test/org/rascalmpl/compiler/lang/rascalcore/check/tests/RunTests.java +++ b/test/org/rascalmpl/compiler/lang/rascalcore/check/tests/RunTests.java @@ -27,11 +27,13 @@ package org.rascalmpl.compiler.lang.rascalcore.check.tests; import org.junit.runner.RunWith; +import org.rascalmpl.test.infrastructure.RascalJUnitParallelRecursiveTestRunner; import org.rascalmpl.test.infrastructure.RascalJUnitTestPrefix; import org.rascalmpl.test.infrastructure.RascalJUnitTestRunner; +import org.rascalmpl.test.infrastructure.RecursiveRascalParallelTest; -@RunWith(RascalJUnitTestRunner.class) -@RascalJUnitTestPrefix("lang::rascalcore::check::tests") +@RunWith(RascalJUnitParallelRecursiveTestRunner.class) +@RecursiveRascalParallelTest("lang::rascalcore::check::tests") public class RunTests { }