Skip to content

Commit 01cc39b

Browse files
committed
Update ModelManagerTests.java
1 parent c468907 commit 01cc39b

File tree

1 file changed

+233
-1
lines changed

1 file changed

+233
-1
lines changed

de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/ModelManagerTests.java

Lines changed: 233 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
package tests.wurstscript.tests;
22

33
import com.google.common.collect.ImmutableSet;
4+
import de.peeeq.wurstio.TimeTaker;
5+
import de.peeeq.wurstio.WurstCompilerJassImpl;
46
import de.peeeq.wurstio.languageserver.BufferManager;
57
import de.peeeq.wurstio.languageserver.ModelManager;
68
import de.peeeq.wurstio.languageserver.ModelManagerImpl;
79
import de.peeeq.wurstio.languageserver.WFile;
810
import de.peeeq.wurstio.utils.FileUtils;
11+
import de.peeeq.wurstscript.RunArgs;
912
import de.peeeq.wurstscript.ast.*;
13+
import de.peeeq.wurstscript.gui.WurstGui;
14+
import de.peeeq.wurstscript.gui.WurstGuiLogger;
15+
import de.peeeq.wurstscript.types.WurstType;
16+
import de.peeeq.wurstscript.types.WurstTypeClass;
17+
import de.peeeq.wurstscript.types.WurstTypeString;
1018
import de.peeeq.wurstscript.utils.Utils;
19+
import de.peeeq.wurstscript.validation.GlobalCaches;
1120
import org.eclipse.lsp4j.Diagnostic;
1221
import org.eclipse.lsp4j.PublishDiagnosticsParams;
1322
import org.hamcrest.CoreMatchers;
@@ -20,6 +29,7 @@
2029
import java.nio.file.Files;
2130
import java.util.HashMap;
2231
import java.util.Map;
32+
import java.util.concurrent.atomic.AtomicBoolean;
2333
import java.util.stream.Collectors;
2434

2535
import static org.hamcrest.CoreMatchers.containsString;
@@ -249,7 +259,7 @@ private Map<WFile, String> keepErrorsInMap(ModelManagerImpl manager) {
249259
results.put(WFile.create(res.getUri()), errors);
250260

251261
for (Diagnostic err : res.getDiagnostics()) {
252-
System.out.println(" " + err.getSeverity() + " in " + res.getUri() + ", line " + err.getRange().getStart().getLine() + "\n " + err.getMessage());
262+
System.out.println(" " + err.getSeverity() + " in " + res.getUri() + ", line " + err.getRange().getStart().getLine() + "\n " + err.getMessage());
253263
}
254264
});
255265
return results;
@@ -587,5 +597,227 @@ public void keepTypeErrorsWhileEditing() throws IOException {
587597
assertThat(errors.get(fileT1), CoreMatchers.containsString("extraneous input '(' expecting NL"));
588598
}
589599

600+
@Test
601+
public void runmapLikePipeline_cacheRegression_closerToRunMap() throws Exception {
602+
File projectFolder = new File("./temp/testProject_runmap_like_real/");
603+
File wurstFolder = new File(projectFolder, "wurst");
604+
newCleanFolder(wurstFolder);
605+
606+
// --- Minimal LinkedList-style module ---
607+
String pkgLinkedList = string(
608+
"package LinkedListModule",
609+
"public module LinkedListModule",
610+
" static function iterator() returns Iterator",
611+
" return new Iterator()",
612+
" static class Iterator",
613+
" LinkedListModule.thistype current = null",
614+
" function hasNext() returns boolean",
615+
" return false",
616+
" function next() returns LinkedListModule.thistype",
617+
" return current",
618+
" function close()",
619+
" destroy this"
620+
);
621+
622+
// --- Minimal string iterator stub to create competing iterator symbol ---
623+
String pkgStrIter = string(
624+
"package StrIter",
625+
"public function string.iterator() returns StringIterator",
626+
" return new StringIterator(this)",
627+
"public class StringIterator",
628+
" string str",
629+
" construct(string s)",
630+
" this.str = s",
631+
" function hasNext() returns boolean",
632+
" return false",
633+
" function next() returns string",
634+
" return \"\"",
635+
" function close()",
636+
" destroy this"
637+
);
638+
639+
// --- Reproducer using both packages ---
640+
String pkgHello = string(
641+
"package Hello",
642+
"import LinkedListModule",
643+
"import StrIter",
644+
"",
645+
"class A",
646+
" use LinkedListModule",
647+
"",
648+
"init",
649+
" let itr = A.iterator()",
650+
" while itr.hasNext()",
651+
" let a = itr.next()",
652+
" destroy a",
653+
" itr.close()",
654+
" let s = \"hello\".iterator()",
655+
" while s.hasNext()",
656+
" BJDebugMsg(s.next())",
657+
" s.close()"
658+
);
659+
660+
// A tiny JASS file so purge logic and JASS transform path look more like RunMap’s environment:
661+
String jass = "globals\nendglobals\n// war3map.j placeholder";
662+
663+
WFile fileLinkedList = WFile.create(new File(wurstFolder, "LinkedListModule.wurst"));
664+
WFile fileStrIter = WFile.create(new File(wurstFolder, "StrIter.wurst"));
665+
WFile fileHello = WFile.create(new File(wurstFolder, "Hello.wurst"));
666+
WFile fileWurst = WFile.create(new File(wurstFolder, "Wurst.wurst"));
667+
WFile fileWar3mapJ = WFile.create(new File(wurstFolder, "war3map.j"));
668+
669+
writeFile(fileLinkedList, pkgLinkedList);
670+
writeFile(fileStrIter, pkgStrIter);
671+
writeFile(fileHello, pkgHello);
672+
writeFile(fileWurst, "package Wurst\n");
673+
writeFile(fileWar3mapJ, jass);
674+
675+
ModelManagerImpl manager = new ModelManagerImpl(projectFolder, new BufferManager());
676+
Map<WFile, String> diags = keepErrorsInMap(manager);
677+
678+
// Baseline build (matches "no IDE errors")
679+
manager.buildProject();
680+
assertEquals(diags.get(fileHello), "", "initial build must be clean");
681+
682+
// VSCode-like reconcile touch:
683+
ModelManager.Changes changes = manager.syncCompilationUnitContent(fileHello, pkgHello + "\n// touch");
684+
manager.reconcile(changes);
685+
assertEquals(diags.get(fileHello), "", "reconcile after harmless edit must be clean");
686+
GlobalCaches.printStats();
687+
688+
// --- First RunMap-like compile on SAME model ---
689+
touchWar3mapJ(manager, fileWar3mapJ, " // pass 1");
690+
runRunmapLikeCompile_Closer(projectFolder, manager);
691+
assertLocalAIsClassA(manager.getCompilationUnit(fileHello));
692+
693+
GlobalCaches.printStats();
694+
695+
// --- Second RunMap-like compile (caches should not corrupt resolution) ---
696+
touchWar3mapJ(manager, fileWar3mapJ, " // pass 2");
697+
runRunmapLikeCompile_Closer(projectFolder, manager);
698+
assertLocalAIsClassA(manager.getCompilationUnit(fileHello));
699+
700+
GlobalCaches.printStats();
701+
}
702+
703+
704+
/** Simulate RunMap.replaceBaseScriptWithConfig(..): rewrite war3map.j inside the SAME model. */
705+
private void touchWar3mapJ(ModelManagerImpl manager, WFile war3map, String suffix) throws IOException {
706+
String content = "globals\nendglobals\n// war3map.j placeholder" + suffix + "\n";
707+
manager.syncCompilationUnitContent(war3map, content);
708+
}
709+
710+
/** Run a RunMap-like compile on the SAME model: purge imports, check → IM, then JASS transform. */
711+
private void runRunmapLikeCompile_Closer(File projectFolder, ModelManagerImpl manager) {
712+
WurstGui gui = new WurstGuiLogger();
713+
TimeTaker time = new TimeTaker.Default();
714+
WurstCompilerJassImpl compiler =
715+
new WurstCompilerJassImpl(time, projectFolder, gui, null, new RunArgs(java.util.Collections.emptyList()));
716+
717+
WurstModel model = manager.getModel();
718+
719+
// 2) Check program
720+
gui.sendProgress("Check program");
721+
compiler.checkProg(model);
722+
if (gui.getErrorCount() > 0) {
723+
throw new AssertionError("RunMap-like checkProg reported errors: " + gui.getErrorList());
724+
}
725+
726+
// 3) Translate to IM
727+
compiler.translateProgToIm(model);
728+
if (gui.getErrorCount() > 0) {
729+
throw new AssertionError("RunMap-like translateProgToIm reported errors: " + gui.getErrorList());
730+
}
731+
732+
// 4) (Optional but closer to reality) Transform to JASS
733+
compiler.transformProgToJass();
734+
// We don't run PJass here; just exercising additional phases & caches.
735+
}
736+
737+
/** Same as MapRequest.purgeUnimportedFiles: keep wurst-folder CUs and any imported transitively, plus .j files. */
738+
private void purgeUnimportedFiles_likeRunMap(WurstModel model, ModelManagerImpl manager) {
739+
// Seed: files inside project root (wurst folder) OR .j files.
740+
java.util.Set<CompilationUnit> keep = model.stream()
741+
.filter(cu -> isInWurstFolder_likeRunMap(cu.getCuInfo().getFile(), manager) || cu.getCuInfo().getFile().endsWith(".j"))
742+
.collect(java.util.stream.Collectors.toSet());
743+
744+
// Recursively add imported packages’ CUs (uses attrImportedPackage like RunMap)
745+
addImports_likeRunMap(keep, keep);
746+
model.removeIf(cu -> !keep.contains(cu));
747+
}
748+
749+
private boolean isInWurstFolder_likeRunMap(String file, ModelManagerImpl manager) {
750+
java.nio.file.Path p = java.nio.file.Paths.get(file);
751+
java.nio.file.Path w = manager.getProjectPath().toPath(); // project root
752+
return p.startsWith(w)
753+
&& java.nio.file.Files.exists(p)
754+
&& Utils.isWurstFile(file);
755+
}
756+
757+
private void addImports_likeRunMap(java.util.Set<CompilationUnit> result, java.util.Set<CompilationUnit> toAdd) {
758+
java.util.Set<CompilationUnit> imported =
759+
toAdd.stream()
760+
.flatMap(cu -> cu.getPackages().stream())
761+
.flatMap(p -> p.getImports().stream())
762+
.map(WImport::attrImportedPackage)
763+
.filter(java.util.Objects::nonNull)
764+
.map(WPackage::attrCompilationUnit)
765+
.collect(java.util.stream.Collectors.toSet());
766+
boolean changed = result.addAll(imported);
767+
if (changed) addImports_likeRunMap(result, imported);
768+
}
769+
770+
/** Assert that 'let a = itr.next()' has type A and that next() returns A. */
771+
private void assertLocalAIsClassA(CompilationUnit cu) {
772+
final AtomicBoolean checked = new AtomicBoolean(false);
773+
final AtomicBoolean didFindA = new AtomicBoolean(false);
774+
final AtomicBoolean didFindString = new AtomicBoolean(false);
775+
776+
cu.accept(new Element.DefaultVisitor() {
777+
@Override public void visit(LocalVarDef l) {
778+
if (!"a".equals(l.getNameId().getName())) { super.visit(l); return; }
779+
780+
VarInitialization init = l.getInitialExpr();
781+
if (init == null) throw new AssertionError("local 'a' has no initializer");
782+
783+
if (init instanceof ExprMemberMethod) {
784+
WurstType t = ((ExprMemberMethod) init).attrTyp();
785+
if (t instanceof WurstTypeClass) {
786+
String cname = ((WurstTypeClass) t).getClassDef().getName();
787+
assertEquals(cname, "A", "Expected local 'a' to be of class A");
788+
} else {
789+
throw new AssertionError("Expected class type for 'a', but got: " + t);
790+
}
791+
}
792+
793+
checked.set(true);
794+
super.visit(l);
795+
}
796+
797+
@Override public void visit(ExprMemberMethodDot call) {
798+
if ("next".equals(call.getFuncName())) {
799+
WurstType rt = call.attrTyp();
800+
if (rt instanceof WurstTypeClass) {
801+
String cname = ((WurstTypeClass) rt).getClassDef().getName();
802+
assertEquals(cname, "A", "next() must return A");
803+
didFindA.set(true);
804+
} else if (rt instanceof WurstTypeString) {
805+
String cname = ((WurstTypeString) rt).getName();
806+
assertEquals(cname, "string", "next() must return string");
807+
didFindString.set(true);
808+
} else {
809+
throw new AssertionError("next() must return class A or string, but was: " + rt);
810+
}
811+
}
812+
super.visit(call);
813+
}
814+
});
815+
816+
if (!checked.get()) throw new AssertionError("Did not find local var 'a' to assert.");
817+
if (!didFindA.get()) throw new AssertionError("Did not find call to next() returning class A.");
818+
if (!didFindString.get()) throw new AssertionError("Did not find call to next() returning string.");
819+
}
820+
821+
590822

591823
}

0 commit comments

Comments
 (0)