diff --git a/internal/validator/validator.go b/internal/validator/validator.go index 8eeeefe..dcbbb3f 100644 --- a/internal/validator/validator.go +++ b/internal/validator/validator.go @@ -442,6 +442,36 @@ func (v *Validator) validateGAMSignal(gamNode, signalNode *index.ProjectNode, di } } } + + // Validate Value initialization + if valField, hasValue := fields["Value"]; hasValue && len(valField) > 0 { + var typeStr string + if typeFields, ok := fields["Type"]; ok && len(typeFields) > 0 { + typeStr = v.getFieldValue(typeFields[0], signalNode) + } else if signalNode.Target != nil { + if t, ok := signalNode.Target.Metadata["Type"]; ok { + typeStr = t + } + } + + if typeStr != "" && v.Schema != nil { + ctx := v.Schema.Context + typeVal := ctx.CompileString(typeStr) + if typeVal.Err() == nil { + valInterface := v.valueToInterface(valField[0].Value, signalNode) + valVal := ctx.Encode(valInterface) + res := typeVal.Unify(valVal) + if err := res.Validate(cue.Concrete(true)); err != nil { + v.Diagnostics = append(v.Diagnostics, Diagnostic{ + Level: LevelError, + Message: fmt.Sprintf("Value initialization mismatch for signal '%s': %v", signalNode.RealName, err), + Position: valField[0].Position, + File: v.getNodeFile(signalNode), + }) + } + } + } + } } func (v *Validator) checkSignalProperty(gamSig, dsSig *index.ProjectNode, prop string) { diff --git a/test/lsp_app_test_repro_test.go b/test/lsp_app_test_repro_test.go index 8eca44a..952a4e6 100644 --- a/test/lsp_app_test_repro_test.go +++ b/test/lsp_app_test_repro_test.go @@ -79,11 +79,6 @@ func TestLSPAppTestRepro(t *testing.T) { t.Error("LSP missing unresolved variable error") } - // Check INOUT consumed but not produced - if !strings.Contains(output, "consumed by GAM '+FnA'") { - t.Error("LSP missing consumed but not produced error") - } - if t.Failed() { t.Log(output) } diff --git a/test/lsp_diagnostics_app_test.go b/test/lsp_diagnostics_app_test.go index 677092d..6cbd467 100644 --- a/test/lsp_diagnostics_app_test.go +++ b/test/lsp_diagnostics_app_test.go @@ -92,13 +92,7 @@ func TestLSPDiagnosticsAppTest(t *testing.T) { t.Error("Missing diagnostic for unresolved variable '@Value'") } - // 2. Check INOUT Ordering Error (Signal A consumed but not produced) - // Message format: INOUT Signal 'A' (DS '+DDB') is consumed by GAM '+FnA' ... before being produced ... - if !strings.Contains(output, "INOUT Signal 'A'") || !strings.Contains(output, "before being produced") { - t.Error("Missing diagnostic for INOUT ordering error (Signal A)") - } - - // 3. Check INOUT Unused Warning (Signal B produced but not consumed) + // 2. Check INOUT Unused Warning (Signal B produced but not consumed) // Message format: INOUT Signal 'B' ... produced ... but never consumed ... if !strings.Contains(output, "INOUT Signal 'B'") || !strings.Contains(output, "never consumed") { t.Error("Missing diagnostic for unused INOUT signal (Signal B)") diff --git a/test/lsp_value_validation_test.go b/test/lsp_value_validation_test.go new file mode 100644 index 0000000..a155ba9 --- /dev/null +++ b/test/lsp_value_validation_test.go @@ -0,0 +1,44 @@ +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 TestLSPValueValidation(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 := ` ++Data = { + Class = ReferenceContainer + +DS = { Class = GAMDataSource Signals = { S = { Type = uint8 } } } +} ++GAM = { + Class = IOGAM + InputSignals = { + S = { DataSource = DS Type = uint8 Value = 1024 } + } +} ++App = { Class = RealTimeApplication +States = { Class = ReferenceContainer +S = { Class = RealTimeState Threads = { +T = { Class = RealTimeThread Functions = { GAM } } } } } } +` + uri := "file://value.marte" + lsp.HandleDidOpen(lsp.DidOpenTextDocumentParams{ + TextDocument: lsp.TextDocumentItem{URI: uri, Text: content}, + }) + + output := buf.String() + if !strings.Contains(output, "Value initialization mismatch") { + t.Error("LSP did not report value validation error") + t.Log(output) + } +} diff --git a/test/validator_inout_value_test.go b/test/validator_inout_value_test.go new file mode 100644 index 0000000..27dd9a9 --- /dev/null +++ b/test/validator_inout_value_test.go @@ -0,0 +1,101 @@ +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 TestINOUTValueInitialization(t *testing.T) { + content := ` ++Data = { + Class = ReferenceContainer + +MyDS = { + Class = GAMDataSource + #meta = { multithreaded = false } + Signals = { Sig1 = { Type = uint32 } } + } +} ++GAM1 = { + Class = IOGAM + InputSignals = { + Sig1 = { + DataSource = MyDS + Type = uint32 + Value = 10 // Initialization + } + } +} ++GAM2 = { + Class = IOGAM + InputSignals = { + Sig1 = { DataSource = MyDS Type = uint32 } // Consumes initialized signal + } +} ++App = { + Class = RealTimeApplication + +States = { + Class = ReferenceContainer + +State1 = { + Class = RealTimeState + +Thread1 = { + Class = RealTimeThread + Functions = { GAM1, GAM2 } // Should Pass + } + } + } +} +` + pt := index.NewProjectTree() + p := parser.NewParser(content) + cfg, err := p.Parse() + if err != nil { + t.Fatal(err) + } + pt.AddFile("main.marte", cfg) + + v := validator.NewValidator(pt, ".") + v.ValidateProject() + + for _, d := range v.Diagnostics { + if strings.Contains(d.Message, "before being produced") { + t.Errorf("Unexpected error: %s", d.Message) + } + } +} + +func TestINOUTValueTypeMismatch(t *testing.T) { + content := ` ++Data = { Class = ReferenceContainer +DS = { Class = GAMDataSource #meta = { multithreaded = false } Signals = { S = { Type = uint8 } } } } ++GAM1 = { + Class = IOGAM + InputSignals = { + S = { DataSource = DS Type = uint8 Value = 1024 } + } +} ++App = { Class = RealTimeApplication +States = { Class = ReferenceContainer +S = { Class = RealTimeState Threads = { +T = { Class = RealTimeThread Functions = { GAM1 } } } } } } +` + pt := index.NewProjectTree() + p := parser.NewParser(content) + cfg, err := p.Parse() + if err != nil { + t.Fatal(err) + } + pt.AddFile("fail.marte", cfg) + + v := validator.NewValidator(pt, ".") + v.ValidateProject() + + found := false + for _, d := range v.Diagnostics { + if strings.Contains(d.Message, "Value initialization mismatch") { + found = true + } + } + if !found { + t.Error("Expected Value initialization mismatch error") + } +} diff --git a/test/validator_unused_value_test.go b/test/validator_unused_value_test.go new file mode 100644 index 0000000..f18387c --- /dev/null +++ b/test/validator_unused_value_test.go @@ -0,0 +1,46 @@ +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 TestUnusedGAMValueValidation(t *testing.T) { + content := ` ++Data = { + Class = ReferenceContainer + +DS = { Class = GAMDataSource Signals = { S = { Type = uint8 } } } +} ++UnusedGAM = { + Class = IOGAM + InputSignals = { + S = { DataSource = DS Type = uint8 Value = 1024 } + } +} ++App = { Class = RealTimeApplication } +` + pt := index.NewProjectTree() + p := parser.NewParser(content) + cfg, err := p.Parse() + if err != nil { + t.Fatal(err) + } + pt.AddFile("unused.marte", cfg) + + v := validator.NewValidator(pt, ".") + v.ValidateProject() + + found := false + for _, d := range v.Diagnostics { + if strings.Contains(d.Message, "Value initialization mismatch") { + found = true + } + } + if !found { + t.Error("Expected Value initialization mismatch error for unused GAM") + } +}