Initial support to variables and to producer/consumer logic

This commit is contained in:
Martino Ferrari
2026-01-28 18:25:48 +01:00
parent b8d45f276d
commit cb79d490e7
8 changed files with 475 additions and 23 deletions

View File

@@ -0,0 +1,45 @@
package integration
import (
"bytes"
"strings"
"testing"
"github.com/marte-community/marte-dev-tools/internal/formatter"
"github.com/marte-community/marte-dev-tools/internal/parser"
)
func TestFormatterVariables(t *testing.T) {
content := `
#var MyInt: int = 10
#var MyStr: string | "A" = "default"
+Obj = {
Field1 = $MyInt
Field2 = $MyStr
}
`
p := parser.NewParser(content)
cfg, err := p.Parse()
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
var buf bytes.Buffer
formatter.Format(cfg, &buf)
output := buf.String()
// Parser reconstructs type expression with spaces
if !strings.Contains(output, "#var MyInt: int = 10") {
t.Errorf("Variable MyInt formatted incorrectly. Got:\n%s", output)
}
// Note: parser adds space after each token in TypeExpr
// string | "A" -> "string | \"A\""
if !strings.Contains(output, "#var MyStr: string | \"A\" = \"default\"") {
t.Errorf("Variable MyStr formatted incorrectly. Got:\n%s", output)
}
if !strings.Contains(output, "Field1 = $MyInt") {
t.Errorf("Variable reference $MyInt formatted incorrectly. Got:\n%s", output)
}
}

73
test/lsp_inout_test.go Normal file
View File

@@ -0,0 +1,73 @@
package integration
import (
"bytes"
"strings"
"testing"
"github.com/marte-community/marte-dev-tools/internal/index"
"github.com/marte-community/marte-dev-tools/internal/lsp"
"github.com/marte-community/marte-dev-tools/internal/schema"
)
func TestLSPINOUTOrdering(t *testing.T) {
lsp.Tree = index.NewProjectTree()
lsp.Documents = make(map[string]string)
// Mock schema if necessary, but we rely on internal schema
lsp.GlobalSchema = schema.LoadFullSchema(".")
var buf bytes.Buffer
lsp.Output = &buf
content := `
+App = {
Class = RealTimeApplication
+Data = {
Class = ReferenceContainer
+DDB = {
Class = GAMDataSource
}
}
+Functions = {
Class = ReferenceContainer
+A = {
Class = IOGAM
InputSignals = {
A = {
DataSource = DDB
Type = uint32
}
}
OutputSignals = {
B = {
DataSource = DDB
Type = uint32
}
}
}
}
+States = {
Class = ReferenceContainer
+State = {
Class =RealTimeState
Threads = {
+Th1 = {
Class = RealTimeThread
Functions = {A}
}
}
}
}
}
`
uri := "file://app.marte"
lsp.HandleDidOpen(lsp.DidOpenTextDocumentParams{
TextDocument: lsp.TextDocumentItem{URI: uri, Text: content},
})
output := buf.String()
if !strings.Contains(output, "INOUT Signal 'A'") {
t.Error("LSP did not report INOUT ordering error")
t.Log(output)
}
}

View File

@@ -0,0 +1,66 @@
package integration
import (
"bytes"
"strings"
"testing"
"github.com/marte-community/marte-dev-tools/internal/index"
"github.com/marte-community/marte-dev-tools/internal/lsp"
"github.com/marte-community/marte-dev-tools/internal/schema"
)
func TestLSPINOUTWarning(t *testing.T) {
lsp.Tree = index.NewProjectTree()
lsp.Documents = make(map[string]string)
lsp.GlobalSchema = schema.LoadFullSchema(".")
var buf bytes.Buffer
lsp.Output = &buf
content := `
+App = {
Class = RealTimeApplication
+Data = {
Class = ReferenceContainer
+DDB = {
Class = GAMDataSource
}
}
+Functions = {
Class = ReferenceContainer
+Producer = {
Class = IOGAM
OutputSignals = {
ProducedSig = {
DataSource = DDB
Type = uint32
}
}
}
}
+States = {
Class = ReferenceContainer
+State = {
Class =RealTimeState
Threads = {
+Th1 = {
Class = RealTimeThread
Functions = {Producer}
}
}
}
}
}
`
uri := "file://warning.marte"
lsp.HandleDidOpen(lsp.DidOpenTextDocumentParams{
TextDocument: lsp.TextDocumentItem{URI: uri, Text: content},
})
output := buf.String()
if !strings.Contains(output, "produced in thread '+Th1' but never consumed") {
t.Error("LSP did not report INOUT usage warning")
t.Log(output)
}
}

View File

@@ -0,0 +1,93 @@
package integration
import (
"strings"
"testing"
"github.com/marte-community/marte-dev-tools/internal/index"
"github.com/marte-community/marte-dev-tools/internal/parser"
"github.com/marte-community/marte-dev-tools/internal/validator"
)
func TestINOUTOrdering(t *testing.T) {
content := `
+Data = {
Class = ReferenceContainer
+MyDS = {
Class = GAMDataSource
#meta = { multithreaded = false } // Explicitly false
Signals = { Sig1 = { Type = uint32 } }
}
}
+GAM_Consumer = {
Class = IOGAM
InputSignals = {
Sig1 = { DataSource = MyDS Type = uint32 }
}
}
+GAM_Producer = {
Class = IOGAM
OutputSignals = {
Sig1 = { DataSource = MyDS Type = uint32 }
}
}
+App = {
Class = RealTimeApplication
+States = {
Class = ReferenceContainer
+State1 = {
Class = RealTimeState
+Thread1 = {
Class = RealTimeThread
Functions = { GAM_Consumer, GAM_Producer } // Fail
}
}
+State2 = {
Class = RealTimeState
+Thread2 = {
Class = RealTimeThread
Functions = { GAM_Producer, GAM_Consumer } // Pass
}
}
}
}
`
pt := index.NewProjectTree()
p := parser.NewParser(content)
cfg, err := p.Parse()
if err != nil {
t.Fatal(err)
}
pt.AddFile("main.marte", cfg)
// Use validator with default schema (embedded)
// We pass "." but it shouldn't matter if no .marte_schema.cue exists
v := validator.NewValidator(pt, ".")
v.ValidateProject()
foundError := false
for _, d := range v.Diagnostics {
if strings.Contains(d.Message, "consumed by GAM '+GAM_Consumer'") &&
strings.Contains(d.Message, "before being produced") {
foundError = true
}
}
if !foundError {
t.Error("Expected INOUT ordering error for State1")
for _, d := range v.Diagnostics {
t.Logf("Diag: %s", d.Message)
}
}
foundErrorState2 := false
for _, d := range v.Diagnostics {
if strings.Contains(d.Message, "State '+State2'") && strings.Contains(d.Message, "before being produced") {
foundErrorState2 = true
}
}
if foundErrorState2 {
t.Error("Unexpected INOUT ordering error for State2 (Correct order)")
}
}

72
test/variables_test.go Normal file
View File

@@ -0,0 +1,72 @@
package integration
import (
"os"
"strings"
"testing"
"github.com/marte-community/marte-dev-tools/internal/builder"
"github.com/marte-community/marte-dev-tools/internal/parser"
)
func TestVariables(t *testing.T) {
content := `
#var MyInt: int = 10
#var MyStr: string = "default"
+Obj = {
Class = Test
Field1 = $MyInt
Field2 = $MyStr
}
`
// Test Parsing
p := parser.NewParser(content)
cfg, err := p.Parse()
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
// Check definitions: #var, #var, +Obj
if len(cfg.Definitions) != 3 {
t.Errorf("Expected 3 definitions, got %d", len(cfg.Definitions))
}
// Test Builder resolution
f, _ := os.CreateTemp("", "vars.marte")
f.WriteString(content)
f.Close()
defer os.Remove(f.Name())
// Build with override
overrides := map[string]string{
"MyInt": "999",
}
b := builder.NewBuilder([]string{f.Name()}, overrides)
outF, _ := os.CreateTemp("", "out.marte")
outName := outF.Name()
defer os.Remove(outName)
err = b.Build(outF)
outF.Close()
if err != nil {
t.Fatalf("Build failed: %v", err)
}
outContent, _ := os.ReadFile(outName)
outStr := string(outContent)
if !strings.Contains(outStr, "Field1 = 999") {
t.Errorf("Variable override failed for MyInt. Got:\n%s", outStr)
}
if !strings.Contains(outStr, "Field2 = \"default\"") {
t.Errorf("Default value failed for MyStr. Got:\n%s", outStr)
}
// Check #var is removed
if strings.Contains(outStr, "#var") {
t.Error("#var definition present in output")
}
}