Improved lsp + builder + using logger
This commit is contained in:
97
test/builder_multifile_test.go
Normal file
97
test/builder_multifile_test.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/marte-dev/marte-dev-tools/internal/builder"
|
||||
)
|
||||
|
||||
func TestMultiFileBuildMergeAndOrder(t *testing.T) {
|
||||
// Setup
|
||||
os.RemoveAll("build_multi_test")
|
||||
os.MkdirAll("build_multi_test", 0755)
|
||||
defer os.RemoveAll("build_multi_test")
|
||||
|
||||
// Create source files
|
||||
// File 1: Has FieldA, no Class.
|
||||
// File 2: Has Class, FieldB.
|
||||
// Both in package +MyObj
|
||||
|
||||
f1Content := `
|
||||
#package Proj.+MyObj
|
||||
FieldA = 10
|
||||
`
|
||||
f2Content := `
|
||||
#package Proj.+MyObj
|
||||
Class = "MyClass"
|
||||
FieldB = 20
|
||||
`
|
||||
ioutil.WriteFile("build_multi_test/f1.marte", []byte(f1Content), 0644)
|
||||
ioutil.WriteFile("build_multi_test/f2.marte", []byte(f2Content), 0644)
|
||||
|
||||
// Execute Build
|
||||
b := builder.NewBuilder([]string{"build_multi_test/f1.marte", "build_multi_test/f2.marte"})
|
||||
|
||||
// Prepare output file
|
||||
// Should be +MyObj.marte (normalized MyObj.marte) - Actually checking content
|
||||
outputFile := "build_multi_test/MyObj.marte"
|
||||
f, err := os.Create(outputFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create output file: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
err = b.Build(f)
|
||||
if err != nil {
|
||||
t.Fatalf("Build failed: %v", err)
|
||||
}
|
||||
f.Close() // Close to flush
|
||||
|
||||
// Check Output
|
||||
if _, err := os.Stat(outputFile); os.IsNotExist(err) {
|
||||
t.Fatalf("Expected output file not found")
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(outputFile)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read output: %v", err)
|
||||
}
|
||||
|
||||
output := string(content)
|
||||
|
||||
// Check presence
|
||||
if !strings.Contains(output, "Class = \"MyClass\"") {
|
||||
t.Error("Output missing Class")
|
||||
}
|
||||
if !strings.Contains(output, "FieldA = 10") {
|
||||
t.Error("Output missing FieldA")
|
||||
}
|
||||
if !strings.Contains(output, "FieldB = 20") {
|
||||
t.Error("Output missing FieldB")
|
||||
}
|
||||
|
||||
// Check Order: Class/FieldB (from f2) should come BEFORE FieldA (from f1)
|
||||
// because f2 has the Class definition.
|
||||
|
||||
idxClass := strings.Index(output, "Class")
|
||||
idxFieldB := strings.Index(output, "FieldB")
|
||||
idxFieldA := strings.Index(output, "FieldA")
|
||||
|
||||
if idxClass == -1 || idxFieldB == -1 || idxFieldA == -1 {
|
||||
t.Fatal("Missing fields in output")
|
||||
}
|
||||
|
||||
// Class should be first
|
||||
if idxClass > idxFieldA {
|
||||
t.Errorf("Expected Class (from f2) to be before FieldA (from f1). Output:\n%s", output)
|
||||
}
|
||||
|
||||
// FieldB should be near Class (same fragment)
|
||||
// FieldA should be after
|
||||
if idxFieldB > idxFieldA {
|
||||
t.Errorf("Expected FieldB (from f2) to be before FieldA (from f1). Output:\n%s", output)
|
||||
}
|
||||
}
|
||||
6
test/integration/hierarchical_dup_1.marte
Normal file
6
test/integration/hierarchical_dup_1.marte
Normal file
@@ -0,0 +1,6 @@
|
||||
#package Proj.DupBase
|
||||
|
||||
+DupObj = {
|
||||
Class = "DupClass"
|
||||
FieldY = 1
|
||||
}
|
||||
3
test/integration/hierarchical_dup_2.marte
Normal file
3
test/integration/hierarchical_dup_2.marte
Normal file
@@ -0,0 +1,3 @@
|
||||
#package Proj.DupBase.DupObj
|
||||
|
||||
FieldY = 2
|
||||
5
test/integration/hierarchical_pkg_1.marte
Normal file
5
test/integration/hierarchical_pkg_1.marte
Normal file
@@ -0,0 +1,5 @@
|
||||
#package Proj.Base
|
||||
|
||||
+MyObj = {
|
||||
Class = "BaseClass"
|
||||
}
|
||||
3
test/integration/hierarchical_pkg_2.marte
Normal file
3
test/integration/hierarchical_pkg_2.marte
Normal file
@@ -0,0 +1,3 @@
|
||||
#package Proj.Base.MyObj
|
||||
|
||||
FieldX = 100
|
||||
6
test/integration/multifile_dup_1.marte
Normal file
6
test/integration/multifile_dup_1.marte
Normal file
@@ -0,0 +1,6 @@
|
||||
#package Proj.TestPackage
|
||||
|
||||
+DupNode = {
|
||||
Class = "DupClass"
|
||||
FieldX = 1
|
||||
}
|
||||
5
test/integration/multifile_dup_2.marte
Normal file
5
test/integration/multifile_dup_2.marte
Normal file
@@ -0,0 +1,5 @@
|
||||
#package Proj.TestPackage
|
||||
|
||||
+DupNode = {
|
||||
FieldX = 2
|
||||
}
|
||||
5
test/integration/multifile_ref_1.marte
Normal file
5
test/integration/multifile_ref_1.marte
Normal file
@@ -0,0 +1,5 @@
|
||||
#package Proj.TestPackage
|
||||
|
||||
+TargetNode = {
|
||||
Class = "TargetClass"
|
||||
}
|
||||
6
test/integration/multifile_ref_2.marte
Normal file
6
test/integration/multifile_ref_2.marte
Normal file
@@ -0,0 +1,6 @@
|
||||
#package Proj.TestPackage
|
||||
|
||||
+SourceNode = {
|
||||
Class = "SourceClass"
|
||||
Target = TargetNode
|
||||
}
|
||||
5
test/integration/multifile_valid_1.marte
Normal file
5
test/integration/multifile_valid_1.marte
Normal file
@@ -0,0 +1,5 @@
|
||||
#package Proj.TestPackage
|
||||
|
||||
+MyNode = {
|
||||
FieldA = 10
|
||||
}
|
||||
6
test/integration/multifile_valid_2.marte
Normal file
6
test/integration/multifile_valid_2.marte
Normal file
@@ -0,0 +1,6 @@
|
||||
#package Proj.TestPackage
|
||||
|
||||
+MyNode = {
|
||||
Class = "MyClass"
|
||||
FieldB = 20
|
||||
}
|
||||
196
test/validator_multifile_test.go
Normal file
196
test/validator_multifile_test.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/marte-dev/marte-dev-tools/internal/index"
|
||||
"github.com/marte-dev/marte-dev-tools/internal/parser"
|
||||
"github.com/marte-dev/marte-dev-tools/internal/validator"
|
||||
)
|
||||
|
||||
func parseAndAddToIndex(t *testing.T, idx *index.ProjectTree, filePath string) {
|
||||
content, err := ioutil.ReadFile(filePath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read %s: %v", filePath, err)
|
||||
}
|
||||
|
||||
p := parser.NewParser(string(content))
|
||||
config, err := p.Parse()
|
||||
if err != nil {
|
||||
t.Fatalf("Parse failed for %s: %v", filePath, err)
|
||||
}
|
||||
|
||||
idx.AddFile(filePath, config)
|
||||
}
|
||||
|
||||
func TestMultiFileNodeValidation(t *testing.T) {
|
||||
idx := index.NewProjectTree()
|
||||
parseAndAddToIndex(t, idx, "integration/multifile_valid_1.marte")
|
||||
parseAndAddToIndex(t, idx, "integration/multifile_valid_2.marte")
|
||||
|
||||
// Resolving references might be needed if the validator relies on it for merging implicitly
|
||||
// But primarily we want to check if the validator sees the merged node.
|
||||
// The current implementation of Validator likely iterates over the ProjectTree.
|
||||
// If the ProjectTree doesn't merge nodes automatically, the Validator needs to do it.
|
||||
// However, the spec says "The build tool, validator, and LSP must merge these definitions".
|
||||
// Let's assume the Validator or Index does the merging logic.
|
||||
|
||||
v := validator.NewValidator(idx)
|
||||
v.ValidateProject()
|
||||
|
||||
// +MyNode is split.
|
||||
// valid_1 has FieldA
|
||||
// valid_2 has Class and FieldB
|
||||
// If merging works, it should have a Class, so no error about missing Class.
|
||||
|
||||
for _, diag := range v.Diagnostics {
|
||||
if strings.Contains(diag.Message, "must contain a 'Class' field") {
|
||||
t.Errorf("Unexpected 'Class' field error for +MyNode: %s", diag.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiFileDuplicateField(t *testing.T) {
|
||||
idx := index.NewProjectTree()
|
||||
parseAndAddToIndex(t, idx, "integration/multifile_dup_1.marte")
|
||||
parseAndAddToIndex(t, idx, "integration/multifile_dup_2.marte")
|
||||
|
||||
v := validator.NewValidator(idx)
|
||||
v.ValidateProject()
|
||||
|
||||
foundError := false
|
||||
for _, diag := range v.Diagnostics {
|
||||
if strings.Contains(diag.Message, "Duplicate Field Definition") && strings.Contains(diag.Message, "FieldX") {
|
||||
foundError = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !foundError {
|
||||
t.Errorf("Expected duplicate field error for FieldX in +DupNode, but found none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiFileReference(t *testing.T) {
|
||||
idx := index.NewProjectTree()
|
||||
parseAndAddToIndex(t, idx, "integration/multifile_ref_1.marte")
|
||||
parseAndAddToIndex(t, idx, "integration/multifile_ref_2.marte")
|
||||
|
||||
idx.ResolveReferences()
|
||||
|
||||
// Check if the reference in +SourceNode to TargetNode is resolved.
|
||||
v := validator.NewValidator(idx)
|
||||
v.ValidateProject()
|
||||
|
||||
if len(v.Diagnostics) > 0 {
|
||||
// Filter out irrelevant errors
|
||||
}
|
||||
}
|
||||
|
||||
func TestHierarchicalPackageMerge(t *testing.T) {
|
||||
idx := index.NewProjectTree()
|
||||
parseAndAddToIndex(t, idx, "integration/hierarchical_pkg_1.marte")
|
||||
parseAndAddToIndex(t, idx, "integration/hierarchical_pkg_2.marte")
|
||||
|
||||
v := validator.NewValidator(idx)
|
||||
v.ValidateProject()
|
||||
|
||||
// +MyObj should have Class (from file 1) and FieldX (from file 2).
|
||||
// If Class is missing, ValidateProject reports error.
|
||||
|
||||
for _, diag := range v.Diagnostics {
|
||||
if strings.Contains(diag.Message, "must contain a 'Class' field") {
|
||||
t.Errorf("Unexpected 'Class' field error for +MyObj: %s", diag.Message)
|
||||
}
|
||||
}
|
||||
|
||||
// We can also inspect the tree to verify FieldX is there (optional, but good for confidence)
|
||||
baseNode := idx.Root.Children["Base"]
|
||||
if baseNode == nil {
|
||||
t.Fatal("Base node not found")
|
||||
}
|
||||
objNode := baseNode.Children["MyObj"]
|
||||
if objNode == nil {
|
||||
t.Fatal("MyObj node not found in Base")
|
||||
}
|
||||
|
||||
hasFieldX := false
|
||||
for _, frag := range objNode.Fragments {
|
||||
for _, def := range frag.Definitions {
|
||||
if f, ok := def.(*parser.Field); ok && f.Name == "FieldX" {
|
||||
hasFieldX = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !hasFieldX {
|
||||
t.Error("FieldX not found in +MyObj")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHierarchicalDuplicate(t *testing.T) {
|
||||
idx := index.NewProjectTree()
|
||||
parseAndAddToIndex(t, idx, "integration/hierarchical_dup_1.marte")
|
||||
parseAndAddToIndex(t, idx, "integration/hierarchical_dup_2.marte")
|
||||
|
||||
v := validator.NewValidator(idx)
|
||||
v.ValidateProject()
|
||||
|
||||
foundError := false
|
||||
for _, diag := range v.Diagnostics {
|
||||
if strings.Contains(diag.Message, "Duplicate Field Definition") && strings.Contains(diag.Message, "FieldY") {
|
||||
foundError = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !foundError {
|
||||
t.Errorf("Expected duplicate field error for FieldY in +DupObj (hierarchical), but found none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsolatedFileValidation(t *testing.T) {
|
||||
idx := index.NewProjectTree()
|
||||
|
||||
// File 1: Has package. Defines SharedClass.
|
||||
f1Content := `
|
||||
#package Proj.Pkg
|
||||
+SharedObj = { Class = SharedClass }
|
||||
`
|
||||
p1 := parser.NewParser(f1Content)
|
||||
c1, _ := p1.Parse()
|
||||
idx.AddFile("shared.marte", c1)
|
||||
|
||||
// File 2: No package. References SharedObj.
|
||||
// Should NOT resolve to SharedObj in shared.marte because iso.marte is isolated.
|
||||
f2Content := `
|
||||
+IsoObj = {
|
||||
Class = "MyClass"
|
||||
Ref = SharedObj
|
||||
}
|
||||
`
|
||||
p2 := parser.NewParser(f2Content)
|
||||
c2, _ := p2.Parse()
|
||||
idx.AddFile("iso.marte", c2)
|
||||
|
||||
idx.ResolveReferences()
|
||||
|
||||
// Find reference
|
||||
var ref *index.Reference
|
||||
for i := range idx.References {
|
||||
if idx.References[i].File == "iso.marte" && idx.References[i].Name == "SharedObj" {
|
||||
ref = &idx.References[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ref == nil {
|
||||
t.Fatal("Reference SharedObj not found in index")
|
||||
}
|
||||
|
||||
if ref.Target != nil {
|
||||
t.Errorf("Expected reference in isolated file to be unresolved, but got target in %s", ref.Target.Fragments[0].File)
|
||||
}
|
||||
}
|
||||
102
test/validator_unused_test.go
Normal file
102
test/validator_unused_test.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/marte-dev/marte-dev-tools/internal/index"
|
||||
"github.com/marte-dev/marte-dev-tools/internal/parser"
|
||||
"github.com/marte-dev/marte-dev-tools/internal/validator"
|
||||
)
|
||||
|
||||
func TestUnusedGAM(t *testing.T) {
|
||||
content := `
|
||||
+MyGAM = {
|
||||
Class = GAMClass
|
||||
+InputSignals = {}
|
||||
}
|
||||
+UsedGAM = {
|
||||
Class = GAMClass
|
||||
+InputSignals = {}
|
||||
}
|
||||
$App = {
|
||||
$Data = {}
|
||||
$States = {
|
||||
$State = {
|
||||
$Threads = {
|
||||
$Thread = {
|
||||
Functions = { UsedGAM }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
p := parser.NewParser(content)
|
||||
config, err := p.Parse()
|
||||
if err != nil {
|
||||
t.Fatalf("Parse failed: %v", err)
|
||||
}
|
||||
|
||||
idx := index.NewProjectTree()
|
||||
idx.AddFile("test.marte", config)
|
||||
idx.ResolveReferences()
|
||||
|
||||
v := validator.NewValidator(idx)
|
||||
v.CheckUnused()
|
||||
|
||||
foundUnused := false
|
||||
for _, d := range v.Diagnostics {
|
||||
if d.Message == "Unused GAM: +MyGAM is defined but not referenced in any thread or scheduler" {
|
||||
foundUnused = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !foundUnused {
|
||||
t.Error("Expected warning for unused GAM +MyGAM, but found none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnusedSignal(t *testing.T) {
|
||||
content := `
|
||||
$App = {
|
||||
$Data = {
|
||||
+MyDS = {
|
||||
Class = DataSourceClass
|
||||
Sig1 = { Type = uint32 }
|
||||
Sig2 = { Type = uint32 }
|
||||
}
|
||||
}
|
||||
}
|
||||
+MyGAM = {
|
||||
Class = GAMClass
|
||||
+InputSignals = {
|
||||
S1 = { DataSource = MyDS Alias = Sig1 }
|
||||
}
|
||||
}
|
||||
`
|
||||
p := parser.NewParser(content)
|
||||
config, err := p.Parse()
|
||||
if err != nil {
|
||||
t.Fatalf("Parse failed: %v", err)
|
||||
}
|
||||
|
||||
idx := index.NewProjectTree()
|
||||
idx.AddFile("test.marte", config)
|
||||
idx.ResolveReferences()
|
||||
|
||||
v := validator.NewValidator(idx)
|
||||
v.CheckUnused()
|
||||
|
||||
foundUnusedSig2 := false
|
||||
for _, d := range v.Diagnostics {
|
||||
if d.Message == "Unused Signal: Sig2 is defined in DataSource +MyDS but never referenced" {
|
||||
foundUnusedSig2 = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !foundUnusedSig2 {
|
||||
t.Error("Expected warning for unused signal Sig2, but found none")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user