Implemented pragmas for not_produced not_consumed signals
This commit is contained in:
2
Makefile
2
Makefile
@@ -10,7 +10,7 @@ build:
|
|||||||
go build -o $(BUILD_DIR)/$(BINARY_NAME) ./cmd/mdt
|
go build -o $(BUILD_DIR)/$(BINARY_NAME) ./cmd/mdt
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test -v ./...
|
go test -v ./test/...
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
go test -cover -coverprofile=coverage.out ./test/... -coverpkg=./internal/...
|
go test -cover -coverprofile=coverage.out ./test/... -coverpkg=./internal/...
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module github.com/marte-community/marte-dev-tools
|
module github.com/marte-community/marte-dev-tools
|
||||||
|
|
||||||
go 1.25.6
|
go 1.25
|
||||||
|
|
||||||
require cuelang.org/go v0.15.3
|
require cuelang.org/go v0.15.3
|
||||||
|
|
||||||
|
|||||||
@@ -45,17 +45,15 @@ func Format(config *parser.Configuration, w io.Writer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func fixComment(text string) string {
|
func fixComment(text string) string {
|
||||||
if strings.HasPrefix(text, "//!") {
|
if !strings.HasPrefix(text, "//!") {
|
||||||
if len(text) > 3 && text[3] != ' ' {
|
if strings.HasPrefix(text, "//#") {
|
||||||
return "//! " + text[3:]
|
if len(text) > 3 && text[3] != ' ' {
|
||||||
}
|
return "//# " + text[3:]
|
||||||
} else if strings.HasPrefix(text, "//#") {
|
}
|
||||||
if len(text) > 3 && text[3] != ' ' {
|
} else if strings.HasPrefix(text, "//") {
|
||||||
return "//# " + text[3:]
|
if len(text) > 2 && text[2] != ' ' && text[2] != '#' && text[2] != '!' {
|
||||||
}
|
return "// " + text[2:]
|
||||||
} else if strings.HasPrefix(text, "//") {
|
}
|
||||||
if len(text) > 2 && text[2] != ' ' && text[2] != '#' && text[2] != '!' {
|
|
||||||
return "// " + text[2:]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return text
|
return text
|
||||||
|
|||||||
@@ -936,6 +936,7 @@ func (v *Validator) CheckINOUTOrdering() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suppress := v.isGloballyAllowed("not_consumed", v.getNodeFile(appNode))
|
||||||
for _, state := range statesNode.Children {
|
for _, state := range statesNode.Children {
|
||||||
var threads []*index.ProjectNode
|
var threads []*index.ProjectNode
|
||||||
for _, child := range state.Children {
|
for _, child := range state.Children {
|
||||||
@@ -961,24 +962,25 @@ func (v *Validator) CheckINOUTOrdering() {
|
|||||||
v.processGAMSignalsForOrdering(gam, "InputSignals", producedSignals, consumedSignals, true, thread, state)
|
v.processGAMSignalsForOrdering(gam, "InputSignals", producedSignals, consumedSignals, true, thread, state)
|
||||||
v.processGAMSignalsForOrdering(gam, "OutputSignals", producedSignals, consumedSignals, false, thread, state)
|
v.processGAMSignalsForOrdering(gam, "OutputSignals", producedSignals, consumedSignals, false, thread, state)
|
||||||
}
|
}
|
||||||
|
if !suppress {
|
||||||
// Check for produced but not consumed
|
// Check for produced but not consumed
|
||||||
for ds, signals := range producedSignals {
|
for ds, signals := range producedSignals {
|
||||||
for sigName, producers := range signals {
|
for sigName, producers := range signals {
|
||||||
consumed := false
|
consumed := false
|
||||||
if cSet, ok := consumedSignals[ds]; ok {
|
if cSet, ok := consumedSignals[ds]; ok {
|
||||||
if cSet[sigName] {
|
if cSet[sigName] {
|
||||||
consumed = true
|
consumed = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if !consumed {
|
||||||
if !consumed {
|
for _, prod := range producers {
|
||||||
for _, prod := range producers {
|
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||||
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
Level: LevelWarning,
|
||||||
Level: LevelWarning,
|
Message: fmt.Sprintf("INOUT Signal '%s' (DS '%s') is produced in thread '%s' but never consumed in the same thread.", sigName, ds.RealName, thread.RealName),
|
||||||
Message: fmt.Sprintf("INOUT Signal '%s' (DS '%s') is produced in thread '%s' but never consumed in the same thread.", sigName, ds.RealName, thread.RealName),
|
Position: v.getNodePosition(prod),
|
||||||
Position: v.getNodePosition(prod),
|
File: v.getNodeFile(prod),
|
||||||
File: v.getNodeFile(prod),
|
})
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -992,7 +994,7 @@ func (v *Validator) processGAMSignalsForOrdering(gam *index.ProjectNode, contain
|
|||||||
if container == nil {
|
if container == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
not_produced_suppress := v.isGloballyAllowed("not_produced", v.getNodeFile(gam))
|
||||||
for _, sig := range container.Children {
|
for _, sig := range container.Children {
|
||||||
fields := v.getFields(sig)
|
fields := v.getFields(sig)
|
||||||
var dsNode *index.ProjectNode
|
var dsNode *index.ProjectNode
|
||||||
@@ -1033,22 +1035,31 @@ func (v *Validator) processGAMSignalsForOrdering(gam *index.ProjectNode, contain
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isInput {
|
if isInput {
|
||||||
isProduced := false
|
if !not_produced_suppress {
|
||||||
if set, ok := produced[dsNode]; ok {
|
isProduced := false
|
||||||
if len(set[sigName]) > 0 {
|
if set, ok := produced[dsNode]; ok {
|
||||||
isProduced = true
|
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{
|
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||||
Level: LevelError,
|
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),
|
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),
|
||||||
Position: v.getNodePosition(sig),
|
Position: v.getNodePosition(sig),
|
||||||
File: v.getNodeFile(sig),
|
File: v.getNodeFile(sig),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
if consumed[dsNode] == nil {
|
if consumed[dsNode] == nil {
|
||||||
consumed[dsNode] = make(map[string]bool)
|
consumed[dsNode] = make(map[string]bool)
|
||||||
}
|
}
|
||||||
@@ -1121,15 +1132,15 @@ func (v *Validator) CheckVariables() {
|
|||||||
|
|
||||||
v.Tree.Walk(checkNodeVars)
|
v.Tree.Walk(checkNodeVars)
|
||||||
}
|
}
|
||||||
func (v *Validator) CheckUnresolvedVariables() {
|
func (v *Validator) CheckUnresolvedVariables() {
|
||||||
for _, ref := range v.Tree.References {
|
for _, ref := range v.Tree.References {
|
||||||
if ref.IsVariable && ref.TargetVariable == nil {
|
if ref.IsVariable && ref.TargetVariable == nil {
|
||||||
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||||
Level: LevelError,
|
Level: LevelError,
|
||||||
Message: fmt.Sprintf("Unresolved variable reference: '@%s'", ref.Name),
|
Message: fmt.Sprintf("Unresolved variable reference: '@%s'", ref.Name),
|
||||||
Position: ref.Position,
|
Position: ref.Position,
|
||||||
File: ref.File,
|
File: ref.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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user