Implemented pragmas for not_produced not_consumed signals

This commit is contained in:
Martino Ferrari
2026-01-30 14:42:26 +01:00
parent c3f4d8f465
commit 6fa67abcb4
5 changed files with 66 additions and 224 deletions

View File

@@ -10,7 +10,7 @@ build:
go build -o $(BUILD_DIR)/$(BINARY_NAME) ./cmd/mdt
test:
go test -v ./...
go test -v ./test/...
coverage:
go test -cover -coverprofile=coverage.out ./test/... -coverpkg=./internal/...

2
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/marte-community/marte-dev-tools
go 1.25.6
go 1.25
require cuelang.org/go v0.15.3

View File

@@ -45,11 +45,8 @@ func Format(config *parser.Configuration, w io.Writer) {
}
func fixComment(text string) string {
if strings.HasPrefix(text, "//!") {
if len(text) > 3 && text[3] != ' ' {
return "//! " + text[3:]
}
} else if strings.HasPrefix(text, "//#") {
if !strings.HasPrefix(text, "//!") {
if strings.HasPrefix(text, "//#") {
if len(text) > 3 && text[3] != ' ' {
return "//# " + text[3:]
}
@@ -58,6 +55,7 @@ func fixComment(text string) string {
return "// " + text[2:]
}
}
}
return text
}

View File

@@ -936,6 +936,7 @@ func (v *Validator) CheckINOUTOrdering() {
return
}
suppress := v.isGloballyAllowed("not_consumed", v.getNodeFile(appNode))
for _, state := range statesNode.Children {
var threads []*index.ProjectNode
for _, child := range state.Children {
@@ -961,7 +962,7 @@ func (v *Validator) CheckINOUTOrdering() {
v.processGAMSignalsForOrdering(gam, "InputSignals", producedSignals, consumedSignals, true, thread, state)
v.processGAMSignalsForOrdering(gam, "OutputSignals", producedSignals, consumedSignals, false, thread, state)
}
if !suppress {
// Check for produced but not consumed
for ds, signals := range producedSignals {
for sigName, producers := range signals {
@@ -986,13 +987,14 @@ func (v *Validator) CheckINOUTOrdering() {
}
}
}
}
func (v *Validator) processGAMSignalsForOrdering(gam *index.ProjectNode, containerName string, produced map[*index.ProjectNode]map[string][]*index.ProjectNode, consumed map[*index.ProjectNode]map[string]bool, isInput bool, thread, state *index.ProjectNode) {
container := gam.Children[containerName]
if container == nil {
return
}
not_produced_suppress := v.isGloballyAllowed("not_produced", v.getNodeFile(gam))
for _, sig := range container.Children {
fields := v.getFields(sig)
var dsNode *index.ProjectNode
@@ -1033,14 +1035,22 @@ func (v *Validator) processGAMSignalsForOrdering(gam *index.ProjectNode, contain
}
if isInput {
if !not_produced_suppress {
isProduced := false
if set, ok := produced[dsNode]; ok {
if len(set[sigName]) > 0 {
isProduced = true
}
}
locally_supressed := false
for _, p := range sig.Pragmas {
if strings.HasPrefix(p, "not_produced:") || strings.HasPrefix(p, "ignore(not_produced)") {
locally_supressed = true
break
}
}
if !isProduced {
if !isProduced && !locally_supressed {
v.Diagnostics = append(v.Diagnostics, Diagnostic{
Level: LevelError,
Message: fmt.Sprintf("INOUT Signal '%s' (DS '%s') is consumed by GAM '%s' in thread '%s' (State '%s') before being produced by any previous GAM.", sigName, dsNode.RealName, gam.RealName, thread.RealName, state.RealName),
@@ -1049,6 +1059,7 @@ func (v *Validator) processGAMSignalsForOrdering(gam *index.ProjectNode, contain
})
}
}
if consumed[dsNode] == nil {
consumed[dsNode] = make(map[string]bool)
}

View File

@@ -1,167 +0,0 @@
package integration
import (
"bufio"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
)
func TestLSPBinaryDiagnostics(t *testing.T) {
// 1. Build mdt
// Ensure we are in test directory context
buildCmd := exec.Command("go", "build", "-o", "../build/mdt", "../cmd/mdt")
if output, err := buildCmd.CombinedOutput(); err != nil {
t.Fatalf("Failed to build mdt: %v\nOutput: %s", err, output)
}
// 2. Start mdt lsp
cmd := exec.Command("../build/mdt", "lsp")
stdin, _ := cmd.StdinPipe()
stdout, _ := cmd.StdoutPipe()
stderr, _ := cmd.StderrPipe()
// Pipe stderr to test log for debugging
go func() {
scanner := bufio.NewScanner(stderr)
for scanner.Scan() {
t.Logf("LSP STDERR: %s", scanner.Text())
}
}()
if err := cmd.Start(); err != nil {
t.Fatalf("Failed to start mdt lsp: %v", err)
}
defer func() {
cmd.Process.Kill()
cmd.Wait()
}()
reader := bufio.NewReader(stdout)
send := func(m interface{}) {
body, _ := json.Marshal(m)
msg := fmt.Sprintf("Content-Length: %d\r\n\r\n%s", len(body), body)
stdin.Write([]byte(msg))
}
readCh := make(chan map[string]interface{}, 100)
go func() { for {
// Parse Header
line, err := reader.ReadString('\n')
if err != nil {
close(readCh)
return
}
var length int
// Handle Content-Length: <len>\r\n
if _, err := fmt.Sscanf(strings.TrimSpace(line), "Content-Length: %d", &length); err != nil {
// Maybe empty line or other header?
continue
}
// Read until empty line (\r\n)
for {
l, err := reader.ReadString('\n')
if err != nil {
close(readCh)
return
}
if l == "\r\n" {
break
}
}
body := make([]byte, length)
if _, err := io.ReadFull(reader, body); err != nil {
close(readCh)
return
}
var m map[string]interface{}
if err := json.Unmarshal(body, &m); err == nil {
readCh <- m
}
}
}()
cwd, _ := os.Getwd()
projectRoot := filepath.Dir(cwd)
absPath := filepath.Join(projectRoot, "examples/app_test.marte")
uri := "file://" + absPath
// 3. Initialize
examplesDir := filepath.Join(projectRoot, "examples")
send(map[string]interface{}{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": map[string]interface{}{
"rootUri": "file://" + examplesDir,
},
})
// 4. Open app_test.marte
content, err := os.ReadFile(absPath)
if err != nil {
t.Fatalf("Failed to read test file: %v", err)
}
send(map[string]interface{}{
"jsonrpc": "2.0",
"method": "textDocument/didOpen",
"params": map[string]interface{}{
"textDocument": map[string]interface{}{
"uri": uri,
"languageId": "marte",
"version": 1,
"text": string(content),
},
},
})
// 5. Wait for diagnostics
foundOrdering := false
foundVariable := false
timeout := time.After(30 * time.Second)
for {
select {
case msg, ok := <-readCh:
if !ok {
t.Fatal("LSP stream closed unexpectedly")
}
t.Logf("Received: %v", msg)
if method, ok := msg["method"].(string); ok && method == "textDocument/publishDiagnostics" {
params := msg["params"].(map[string]interface{})
// Check URI match?
// if params["uri"] != uri { continue } // Might be absolute vs relative
diags := params["diagnostics"].([]interface{})
for _, d := range diags {
m := d.(map[string]interface{})["message"].(string)
if strings.Contains(m, "INOUT Signal 'A'") {
foundOrdering = true
t.Log("Found Ordering error")
}
if strings.Contains(m, "Unresolved variable reference: '@Value'") {
foundVariable = true
t.Log("Found Variable error")
}
}
if foundOrdering && foundVariable {
return // Success
}
}
case <-timeout:
t.Fatal("Timeout waiting for diagnostics")
}
}
}