Added project schema feature
This commit is contained in:
@@ -81,7 +81,7 @@ func runCheck(args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// idx.ResolveReferences() // Not implemented in new tree yet, but Validator uses Tree directly
|
// idx.ResolveReferences() // Not implemented in new tree yet, but Validator uses Tree directly
|
||||||
v := validator.NewValidator(tree)
|
v := validator.NewValidator(tree, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
|
|
||||||
// Legacy loop removed as ValidateProject covers it via recursion
|
// Legacy loop removed as ValidateProject covers it via recursion
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ type TextEdit struct {
|
|||||||
|
|
||||||
var tree = index.NewProjectTree()
|
var tree = index.NewProjectTree()
|
||||||
var documents = make(map[string]string)
|
var documents = make(map[string]string)
|
||||||
|
var projectRoot string
|
||||||
|
|
||||||
func RunServer() {
|
func RunServer() {
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
@@ -193,6 +194,7 @@ func handleMessage(msg *JsonRpcMessage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if root != "" {
|
if root != "" {
|
||||||
|
projectRoot = root
|
||||||
logger.Printf("Scanning workspace: %s\n", root)
|
logger.Printf("Scanning workspace: %s\n", root)
|
||||||
tree.ScanDirectory(root)
|
tree.ScanDirectory(root)
|
||||||
tree.ResolveReferences()
|
tree.ResolveReferences()
|
||||||
@@ -323,7 +325,7 @@ func handleFormatting(params DocumentFormattingParams) []TextEdit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runValidation(uri string) {
|
func runValidation(uri string) {
|
||||||
v := validator.NewValidator(tree)
|
v := validator.NewValidator(tree, projectRoot)
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
v.CheckUnused()
|
v.CheckUnused()
|
||||||
|
|
||||||
|
|||||||
@@ -114,11 +114,32 @@
|
|||||||
"BaseLib2GAM": { "fields": [] },
|
"BaseLib2GAM": { "fields": [] },
|
||||||
"ConversionGAM": { "fields": [] },
|
"ConversionGAM": { "fields": [] },
|
||||||
"DoubleHandshakeGAM": { "fields": [] },
|
"DoubleHandshakeGAM": { "fields": [] },
|
||||||
"FilterGAM": { "fields": [] },
|
"FilterGAM": {
|
||||||
"HistogramGAM": { "fields": [] },
|
"fields": [
|
||||||
|
{"name": "Num", "type": "array", "mandatory": true},
|
||||||
|
{"name": "Den", "type": "array", "mandatory": true},
|
||||||
|
{"name": "ResetInEachState", "type": "any", "mandatory": false},
|
||||||
|
{"name": "InputSignals", "type": "node", "mandatory": false},
|
||||||
|
{"name": "OutputSignals", "type": "node", "mandatory": false}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"HistogramGAM": {
|
||||||
|
"fields": [
|
||||||
|
{"name": "BeginCycleNumber", "type": "int", "mandatory": false},
|
||||||
|
{"name": "StateChangeResetName", "type": "string", "mandatory": false},
|
||||||
|
{"name": "InputSignals", "type": "node", "mandatory": false},
|
||||||
|
{"name": "OutputSignals", "type": "node", "mandatory": false}
|
||||||
|
]
|
||||||
|
},
|
||||||
"Interleaved2FlatGAM": { "fields": [] },
|
"Interleaved2FlatGAM": { "fields": [] },
|
||||||
"FlattenedStructIOGAM": { "fields": [] },
|
"FlattenedStructIOGAM": { "fields": [] },
|
||||||
"MathExpressionGAM": { "fields": [] },
|
"MathExpressionGAM": {
|
||||||
|
"fields": [
|
||||||
|
{"name": "Expression", "type": "string", "mandatory": true},
|
||||||
|
{"name": "InputSignals", "type": "node", "mandatory": false},
|
||||||
|
{"name": "OutputSignals", "type": "node", "mandatory": false}
|
||||||
|
]
|
||||||
|
},
|
||||||
"MessageGAM": { "fields": [] },
|
"MessageGAM": { "fields": [] },
|
||||||
"MuxGAM": { "fields": [] },
|
"MuxGAM": { "fields": [] },
|
||||||
"SimulinkWrapperGAM": { "fields": [] },
|
"SimulinkWrapperGAM": { "fields": [] },
|
||||||
@@ -128,10 +149,42 @@
|
|||||||
"TriggeredIOGAM": { "fields": [] },
|
"TriggeredIOGAM": { "fields": [] },
|
||||||
"WaveformGAM": { "fields": [] },
|
"WaveformGAM": { "fields": [] },
|
||||||
"DAN": { "fields": [] },
|
"DAN": { "fields": [] },
|
||||||
"LinuxTimer": { "fields": [] },
|
"LinuxTimer": {
|
||||||
|
"fields": [
|
||||||
|
{"name": "ExecutionMode", "type": "string", "mandatory": false},
|
||||||
|
{"name": "SleepNature", "type": "string", "mandatory": false},
|
||||||
|
{"name": "SleepPercentage", "type": "any", "mandatory": false},
|
||||||
|
{"name": "Phase", "type": "int", "mandatory": false},
|
||||||
|
{"name": "CPUMask", "type": "int", "mandatory": false},
|
||||||
|
{"name": "TimeProvider", "type": "node", "mandatory": false},
|
||||||
|
{"name": "Signals", "type": "node", "mandatory": true}
|
||||||
|
]
|
||||||
|
},
|
||||||
"LinkDataSource": { "fields": [] },
|
"LinkDataSource": { "fields": [] },
|
||||||
"MDSReader": { "fields": [] },
|
"MDSReader": {
|
||||||
"MDSWriter": { "fields": [] },
|
"fields": [
|
||||||
|
{"name": "TreeName", "type": "string", "mandatory": true},
|
||||||
|
{"name": "ShotNumber", "type": "int", "mandatory": true},
|
||||||
|
{"name": "Frequency", "type": "float", "mandatory": true},
|
||||||
|
{"name": "Signals", "type": "node", "mandatory": true}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"MDSWriter": {
|
||||||
|
"fields": [
|
||||||
|
{"name": "NumberOfBuffers", "type": "int", "mandatory": true},
|
||||||
|
{"name": "CPUMask", "type": "int", "mandatory": true},
|
||||||
|
{"name": "StackSize", "type": "int", "mandatory": true},
|
||||||
|
{"name": "TreeName", "type": "string", "mandatory": true},
|
||||||
|
{"name": "PulseNumber", "type": "int", "mandatory": false},
|
||||||
|
{"name": "StoreOnTrigger", "type": "int", "mandatory": true},
|
||||||
|
{"name": "EventName", "type": "string", "mandatory": true},
|
||||||
|
{"name": "TimeRefresh", "type": "float", "mandatory": true},
|
||||||
|
{"name": "NumberOfPreTriggers", "type": "int", "mandatory": false},
|
||||||
|
{"name": "NumberOfPostTriggers", "type": "int", "mandatory": false},
|
||||||
|
{"name": "Signals", "type": "node", "mandatory": true},
|
||||||
|
{"name": "Messages", "type": "node", "mandatory": false}
|
||||||
|
]
|
||||||
|
},
|
||||||
"NI1588TimeStamp": { "fields": [] },
|
"NI1588TimeStamp": { "fields": [] },
|
||||||
"NI6259ADC": { "fields": [] },
|
"NI6259ADC": { "fields": [] },
|
||||||
"NI6259DAC": { "fields": [] },
|
"NI6259DAC": { "fields": [] },
|
||||||
@@ -153,4 +206,4 @@
|
|||||||
"OPCUA": { "fields": [] },
|
"OPCUA": { "fields": [] },
|
||||||
"SysLogger": { "fields": [] }
|
"SysLogger": { "fields": [] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed marte.json
|
//go:embed marte.json
|
||||||
@@ -45,11 +46,89 @@ func LoadSchema(path string) (*Schema, error) {
|
|||||||
return &s, nil
|
return &s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultSchema returns a built-in schema with core MARTe classes
|
// DefaultSchema returns the built-in embedded schema
|
||||||
func DefaultSchema() *Schema {
|
func DefaultSchema() *Schema {
|
||||||
var s Schema
|
var s Schema
|
||||||
if err := json.Unmarshal(defaultSchemaJSON, &s); err != nil {
|
if err := json.Unmarshal(defaultSchemaJSON, &s); err != nil {
|
||||||
panic(fmt.Sprintf("failed to parse default embedded schema: %v", err))
|
panic(fmt.Sprintf("failed to parse default embedded schema: %v", err))
|
||||||
}
|
}
|
||||||
|
if s.Classes == nil {
|
||||||
|
s.Classes = make(map[string]ClassDefinition)
|
||||||
|
}
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Merge adds rules from 'other' to 's'.
|
||||||
|
// Rules for the same class are merged (new fields added, existing fields updated).
|
||||||
|
func (s *Schema) Merge(other *Schema) {
|
||||||
|
if other == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for className, classDef := range other.Classes {
|
||||||
|
if existingClass, ok := s.Classes[className]; ok {
|
||||||
|
// Merge fields
|
||||||
|
fieldMap := make(map[string]FieldDefinition)
|
||||||
|
for _, f := range classDef.Fields {
|
||||||
|
fieldMap[f.Name] = f
|
||||||
|
}
|
||||||
|
|
||||||
|
var mergedFields []FieldDefinition
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
|
||||||
|
// Keep existing fields, update if present in other
|
||||||
|
for _, f := range existingClass.Fields {
|
||||||
|
if newF, ok := fieldMap[f.Name]; ok {
|
||||||
|
mergedFields = append(mergedFields, newF)
|
||||||
|
} else {
|
||||||
|
mergedFields = append(mergedFields, f)
|
||||||
|
}
|
||||||
|
seen[f.Name] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append new fields
|
||||||
|
for _, f := range classDef.Fields {
|
||||||
|
if !seen[f.Name] {
|
||||||
|
mergedFields = append(mergedFields, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
existingClass.Fields = mergedFields
|
||||||
|
if classDef.Ordered {
|
||||||
|
existingClass.Ordered = true
|
||||||
|
}
|
||||||
|
s.Classes[className] = existingClass
|
||||||
|
} else {
|
||||||
|
s.Classes[className] = classDef
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadFullSchema(projectRoot string) *Schema {
|
||||||
|
s := DefaultSchema()
|
||||||
|
|
||||||
|
// 1. System Paths
|
||||||
|
sysPaths := []string{
|
||||||
|
"/usr/share/mdt/marte_schema.json",
|
||||||
|
}
|
||||||
|
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err == nil {
|
||||||
|
sysPaths = append(sysPaths, filepath.Join(home, ".local/share/mdt/marte_schema.json"))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, path := range sysPaths {
|
||||||
|
if sysSchema, err := LoadSchema(path); err == nil {
|
||||||
|
s.Merge(sysSchema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Project Path
|
||||||
|
if projectRoot != "" {
|
||||||
|
projectSchemaPath := filepath.Join(projectRoot, ".marte_schema.json")
|
||||||
|
if projSchema, err := LoadSchema(projectSchemaPath); err == nil {
|
||||||
|
s.Merge(projSchema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
@@ -27,10 +27,10 @@ type Validator struct {
|
|||||||
Schema *schema.Schema
|
Schema *schema.Schema
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewValidator(tree *index.ProjectTree) *Validator {
|
func NewValidator(tree *index.ProjectTree, projectRoot string) *Validator {
|
||||||
return &Validator{
|
return &Validator{
|
||||||
Tree: tree,
|
Tree: tree,
|
||||||
Schema: schema.DefaultSchema(),
|
Schema: schema.LoadFullSchema(projectRoot),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -153,6 +153,12 @@ The tool must build an index of the configuration to support LSP features and va
|
|||||||
- **Schema Definition**:
|
- **Schema Definition**:
|
||||||
- Class validation rules must be defined in a separate schema file.
|
- Class validation rules must be defined in a separate schema file.
|
||||||
- **Project-Specific Classes**: Developers can define their own project-specific classes and corresponding validation rules, expanding the validation capabilities for their specific needs.
|
- **Project-Specific Classes**: Developers can define their own project-specific classes and corresponding validation rules, expanding the validation capabilities for their specific needs.
|
||||||
|
- **Schema Loading**:
|
||||||
|
- **Default Schema**: The tool should look for a default schema file `marte_schema.json` in standard system locations:
|
||||||
|
- `/usr/share/mdt/marte_schema.json`
|
||||||
|
- `$HOME/.local/share/mdt/marte_schema.json`
|
||||||
|
- **Project Schema**: If a file named `.marte_schema.json` exists in the project root, it must be loaded.
|
||||||
|
- **Merging**: The final schema is a merge of the built-in schema, the system default schema (if found), and the project-specific schema. Rules in later sources (Project > System > Built-in) append to or override earlier ones.
|
||||||
- **Duplicate Fields**:
|
- **Duplicate Fields**:
|
||||||
- **Constraint**: A field must not be defined more than once within the same object/node scope, even if those definitions are spread across different files.
|
- **Constraint**: A field must not be defined more than once within the same object/node scope, even if those definitions are spread across different files.
|
||||||
- **Multi-File Consideration**: Validation must account for nodes being defined across multiple files (merged) when checking for duplicates.
|
- **Multi-File Consideration**: Validation must account for nodes being defined across multiple files (merged) when checking for duplicates.
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func TestCheckCommand(t *testing.T) {
|
|||||||
idx := index.NewProjectTree()
|
idx := index.NewProjectTree()
|
||||||
idx.AddFile(inputFile, config)
|
idx.AddFile(inputFile, config)
|
||||||
|
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
v.CheckUnused()
|
v.CheckUnused()
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ func TestCheckDuplicate(t *testing.T) {
|
|||||||
idx := index.NewProjectTree()
|
idx := index.NewProjectTree()
|
||||||
idx.AddFile(inputFile, config)
|
idx.AddFile(inputFile, config)
|
||||||
|
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
|
|
||||||
foundError := false
|
foundError := false
|
||||||
@@ -95,7 +95,7 @@ func TestSignalNoClassValidation(t *testing.T) {
|
|||||||
idx := index.NewProjectTree()
|
idx := index.NewProjectTree()
|
||||||
idx.AddFile(inputFile, config)
|
idx.AddFile(inputFile, config)
|
||||||
|
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
|
|
||||||
if len(v.Diagnostics) > 0 {
|
if len(v.Diagnostics) > 0 {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ func TestLSPDiagnostics(t *testing.T) {
|
|||||||
idx := index.NewProjectTree()
|
idx := index.NewProjectTree()
|
||||||
idx.AddFile(inputFile, config)
|
idx.AddFile(inputFile, config)
|
||||||
|
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
|
|
||||||
// Check for expected diagnostics
|
// Check for expected diagnostics
|
||||||
|
|||||||
83
test/validator_analyzed_test.go
Normal file
83
test/validator_analyzed_test.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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 TestMDSWriterValidation(t *testing.T) {
|
||||||
|
// MDSWriter requires TreeName, NumberOfBuffers, etc.
|
||||||
|
content := `
|
||||||
|
+MyMDSWriter = {
|
||||||
|
Class = MDSWriter
|
||||||
|
NumberOfBuffers = 10
|
||||||
|
CPUMask = 1
|
||||||
|
StackSize = 1000000
|
||||||
|
// Missing TreeName
|
||||||
|
StoreOnTrigger = 0
|
||||||
|
EventName = "Update"
|
||||||
|
TimeRefresh = 1.0
|
||||||
|
+Signals = {}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
p := parser.NewParser(content)
|
||||||
|
config, err := p.Parse()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
idx := index.NewProjectTree()
|
||||||
|
idx.AddFile("mdswriter.marte", config)
|
||||||
|
|
||||||
|
v := validator.NewValidator(idx, ".")
|
||||||
|
v.ValidateProject()
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, d := range v.Diagnostics {
|
||||||
|
if strings.Contains(d.Message, "Missing mandatory field 'TreeName'") {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Error("Expected error for missing 'TreeName' in MDSWriter")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMathExpressionGAMValidation(t *testing.T) {
|
||||||
|
// MathExpressionGAM requires Expression
|
||||||
|
content := `
|
||||||
|
+MyMath = {
|
||||||
|
Class = MathExpressionGAM
|
||||||
|
// Missing Expression
|
||||||
|
}
|
||||||
|
`
|
||||||
|
p := parser.NewParser(content)
|
||||||
|
config, err := p.Parse()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
idx := index.NewProjectTree()
|
||||||
|
idx.AddFile("math.marte", config)
|
||||||
|
|
||||||
|
v := validator.NewValidator(idx, ".")
|
||||||
|
v.ValidateProject()
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, d := range v.Diagnostics {
|
||||||
|
if strings.Contains(d.Message, "Missing mandatory field 'Expression'") {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Error("Expected error for missing 'Expression' in MathExpressionGAM")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ func TestPIDGAMValidation(t *testing.T) {
|
|||||||
idx := index.NewProjectTree()
|
idx := index.NewProjectTree()
|
||||||
idx.AddFile("pid.marte", config)
|
idx.AddFile("pid.marte", config)
|
||||||
|
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
|
|
||||||
foundKi := false
|
foundKi := false
|
||||||
@@ -68,7 +68,7 @@ func TestFileDataSourceValidation(t *testing.T) {
|
|||||||
idx := index.NewProjectTree()
|
idx := index.NewProjectTree()
|
||||||
idx.AddFile("file.marte", config)
|
idx.AddFile("file.marte", config)
|
||||||
|
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func TestRealTimeApplicationValidation(t *testing.T) {
|
|||||||
idx := index.NewProjectTree()
|
idx := index.NewProjectTree()
|
||||||
idx.AddFile("app.marte", config)
|
idx.AddFile("app.marte", config)
|
||||||
|
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
|
|
||||||
missingData := false
|
missingData := false
|
||||||
@@ -68,7 +68,7 @@ func TestGAMSchedulerValidation(t *testing.T) {
|
|||||||
idx := index.NewProjectTree()
|
idx := index.NewProjectTree()
|
||||||
idx.AddFile("scheduler.marte", config)
|
idx.AddFile("scheduler.marte", config)
|
||||||
|
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func TestSDNSubscriberValidation(t *testing.T) {
|
|||||||
idx := index.NewProjectTree()
|
idx := index.NewProjectTree()
|
||||||
idx.AddFile("sdn.marte", config)
|
idx.AddFile("sdn.marte", config)
|
||||||
|
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
@@ -60,7 +60,7 @@ func TestFileWriterValidation(t *testing.T) {
|
|||||||
idx := index.NewProjectTree()
|
idx := index.NewProjectTree()
|
||||||
idx.AddFile("writer.marte", config)
|
idx.AddFile("writer.marte", config)
|
||||||
|
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func TestMultiFileNodeValidation(t *testing.T) {
|
|||||||
// However, the spec says "The build tool, validator, and LSP must merge these definitions".
|
// 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.
|
// Let's assume the Validator or Index does the merging logic.
|
||||||
|
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
|
|
||||||
// +MyNode is split.
|
// +MyNode is split.
|
||||||
@@ -57,7 +57,7 @@ func TestMultiFileDuplicateField(t *testing.T) {
|
|||||||
parseAndAddToIndex(t, idx, "integration/multifile_dup_1.marte")
|
parseAndAddToIndex(t, idx, "integration/multifile_dup_1.marte")
|
||||||
parseAndAddToIndex(t, idx, "integration/multifile_dup_2.marte")
|
parseAndAddToIndex(t, idx, "integration/multifile_dup_2.marte")
|
||||||
|
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
|
|
||||||
foundError := false
|
foundError := false
|
||||||
@@ -81,7 +81,7 @@ func TestMultiFileReference(t *testing.T) {
|
|||||||
idx.ResolveReferences()
|
idx.ResolveReferences()
|
||||||
|
|
||||||
// Check if the reference in +SourceNode to TargetNode is resolved.
|
// Check if the reference in +SourceNode to TargetNode is resolved.
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
|
|
||||||
if len(v.Diagnostics) > 0 {
|
if len(v.Diagnostics) > 0 {
|
||||||
@@ -94,7 +94,7 @@ func TestHierarchicalPackageMerge(t *testing.T) {
|
|||||||
parseAndAddToIndex(t, idx, "integration/hierarchical_pkg_1.marte")
|
parseAndAddToIndex(t, idx, "integration/hierarchical_pkg_1.marte")
|
||||||
parseAndAddToIndex(t, idx, "integration/hierarchical_pkg_2.marte")
|
parseAndAddToIndex(t, idx, "integration/hierarchical_pkg_2.marte")
|
||||||
|
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
|
|
||||||
// +MyObj should have Class (from file 1) and FieldX (from file 2).
|
// +MyObj should have Class (from file 1) and FieldX (from file 2).
|
||||||
@@ -135,7 +135,7 @@ func TestHierarchicalDuplicate(t *testing.T) {
|
|||||||
parseAndAddToIndex(t, idx, "integration/hierarchical_dup_1.marte")
|
parseAndAddToIndex(t, idx, "integration/hierarchical_dup_1.marte")
|
||||||
parseAndAddToIndex(t, idx, "integration/hierarchical_dup_2.marte")
|
parseAndAddToIndex(t, idx, "integration/hierarchical_dup_2.marte")
|
||||||
|
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
|
|
||||||
foundError := false
|
foundError := false
|
||||||
|
|||||||
71
test/validator_project_schema_test.go
Normal file
71
test/validator_project_schema_test.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"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 TestProjectSpecificSchema(t *testing.T) {
|
||||||
|
// Create temp dir
|
||||||
|
tmpDir, err := os.MkdirTemp("", "mdt_test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
// Define project schema
|
||||||
|
schemaContent := `
|
||||||
|
{
|
||||||
|
"classes": {
|
||||||
|
"ProjectClass": {
|
||||||
|
"fields": [
|
||||||
|
{"name": "CustomField", "type": "int", "mandatory": true}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
err = os.WriteFile(filepath.Join(tmpDir, ".marte_schema.json"), []byte(schemaContent), 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define MARTe file using ProjectClass
|
||||||
|
marteContent := `
|
||||||
|
+Obj = {
|
||||||
|
Class = ProjectClass
|
||||||
|
// Missing CustomField
|
||||||
|
}
|
||||||
|
`
|
||||||
|
// We parse the content in memory, but we need the validator to look in tmpDir
|
||||||
|
p := parser.NewParser(marteContent)
|
||||||
|
config, err := p.Parse()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
idx := index.NewProjectTree()
|
||||||
|
idx.AddFile("project.marte", config)
|
||||||
|
|
||||||
|
// Pass tmpDir as projectRoot
|
||||||
|
v := validator.NewValidator(idx, tmpDir)
|
||||||
|
v.ValidateProject()
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, d := range v.Diagnostics {
|
||||||
|
if strings.Contains(d.Message, "Missing mandatory field 'CustomField'") {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Error("Expected error for missing 'CustomField' defined in project schema")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ func TestSchemaValidationMandatory(t *testing.T) {
|
|||||||
idx := index.NewProjectTree()
|
idx := index.NewProjectTree()
|
||||||
idx.AddFile("test.marte", config)
|
idx.AddFile("test.marte", config)
|
||||||
|
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
@@ -60,7 +60,7 @@ func TestSchemaValidationType(t *testing.T) {
|
|||||||
idx := index.NewProjectTree()
|
idx := index.NewProjectTree()
|
||||||
idx.AddFile("test.marte", config)
|
idx.AddFile("test.marte", config)
|
||||||
|
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
@@ -94,7 +94,7 @@ func TestSchemaValidationOrder(t *testing.T) {
|
|||||||
idx := index.NewProjectTree()
|
idx := index.NewProjectTree()
|
||||||
idx.AddFile("test.marte", config)
|
idx.AddFile("test.marte", config)
|
||||||
|
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
|
|
||||||
found := false
|
found := false
|
||||||
@@ -127,7 +127,7 @@ func TestSchemaValidationMandatoryNode(t *testing.T) {
|
|||||||
idx := index.NewProjectTree()
|
idx := index.NewProjectTree()
|
||||||
idx.AddFile("test.marte", config)
|
idx.AddFile("test.marte", config)
|
||||||
|
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
|
|
||||||
for _, d := range v.Diagnostics {
|
for _, d := range v.Diagnostics {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ $App = {
|
|||||||
idx.AddFile("test.marte", config)
|
idx.AddFile("test.marte", config)
|
||||||
idx.ResolveReferences()
|
idx.ResolveReferences()
|
||||||
|
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.CheckUnused()
|
v.CheckUnused()
|
||||||
|
|
||||||
foundUnused := false
|
foundUnused := false
|
||||||
@@ -85,7 +85,7 @@ $App = {
|
|||||||
idx.AddFile("test.marte", config)
|
idx.AddFile("test.marte", config)
|
||||||
idx.ResolveReferences()
|
idx.ResolveReferences()
|
||||||
|
|
||||||
v := validator.NewValidator(idx)
|
v := validator.NewValidator(idx, ".")
|
||||||
v.CheckUnused()
|
v.CheckUnused()
|
||||||
|
|
||||||
foundUnusedSig2 := false
|
foundUnusedSig2 := false
|
||||||
|
|||||||
Reference in New Issue
Block a user