Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions concore_cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ Checks:
- File references and naming conventions
- ZMQ vs file-based communication

**Options:**
- `-s, --source <dir>` - Source directory (default: src)

**Example:**
```bash
concore validate workflow.graphml
Expand Down
7 changes: 5 additions & 2 deletions concore_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,13 @@ def run(workflow_file, source, output, type, auto_build):

@cli.command()
@click.argument('workflow_file', type=click.Path(exists=True))
def validate(workflow_file):
@click.option('--source', '-s', default='src', help='Source directory')
def validate(workflow_file, source):
"""Validate a workflow file"""
try:
validate_workflow(workflow_file, console)
ok = validate_workflow(workflow_file, source, console)
if not ok:
sys.exit(1)
except Exception as e:
console.print(f"[red]Error:[/red] {str(e)}")
sys.exit(1)
Expand Down
28 changes: 21 additions & 7 deletions concore_cli/commands/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
import re
import xml.etree.ElementTree as ET

def validate_workflow(workflow_file, console):
def validate_workflow(workflow_file, source_dir, console):
workflow_path = Path(workflow_file)
source_root = (workflow_path.parent / source_dir)

console.print(f"[cyan]Validating:[/cyan] {workflow_path.name}")
console.print()
Expand All @@ -15,31 +16,35 @@ def validate_workflow(workflow_file, console):
warnings = []
info = []

def finalize():
show_results(console, errors, warnings, info)
return len(errors) == 0

try:
with open(workflow_path, 'r') as f:
content = f.read()

if not content.strip():
errors.append("File is empty")
return show_results(console, errors, warnings, info)
return finalize()

# strict XML syntax check
try:
ET.fromstring(content)
except ET.ParseError as e:
errors.append(f"Invalid XML: {str(e)}")
return show_results(console, errors, warnings, info)
return finalize()

try:
soup = BeautifulSoup(content, 'xml')
except Exception as e:
errors.append(f"Invalid XML: {str(e)}")
return show_results(console, errors, warnings, info)
return finalize()

root = soup.find('graphml')
if not root:
errors.append("Not a valid GraphML file - missing <graphml> root element")
return show_results(console, errors, warnings, info)
return finalize()

# check the graph attributes
graph = soup.find('graph')
Expand All @@ -64,6 +69,9 @@ def validate_workflow(workflow_file, console):
warnings.append("No edges found in workflow")
else:
info.append(f"Found {len(edges)} edge(s)")

if not source_root.exists():
warnings.append(f"Source directory not found: {source_root}")

node_labels = []
for node in nodes:
Expand Down Expand Up @@ -96,6 +104,10 @@ def validate_workflow(workflow_file, console):
errors.append(f"Node '{label}' has no filename")
elif not any(filename.endswith(ext) for ext in ['.py', '.cpp', '.m', '.v', '.java']):
warnings.append(f"Node '{label}' has unusual file extension")
elif source_root.exists():
file_path = source_root / filename
if not file_path.exists():
errors.append(f"Missing source file: {filename}")
else:
warnings.append(f"Node {node_id} has no label")
except Exception as e:
Expand Down Expand Up @@ -138,12 +150,14 @@ def validate_workflow(workflow_file, console):
if file_edges > 0:
info.append(f"File-based edges: {file_edges}")

show_results(console, errors, warnings, info)
return finalize()

except FileNotFoundError:
console.print(f"[red]Error:[/red] File not found: {workflow_path}")
return False
except Exception as e:
console.print(f"[red]Validation failed:[/red] {str(e)}")
return False

def show_results(console, errors, warnings, info):
if errors:
Expand Down
13 changes: 13 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,19 @@ def test_validate_valid_file(self):
result = self.runner.invoke(cli, ['validate', 'test-project/workflow.graphml'])
self.assertEqual(result.exit_code, 0)
self.assertIn('Validation passed', result.output)

def test_validate_missing_node_file(self):
with self.runner.isolated_filesystem(temp_dir=self.temp_dir):
result = self.runner.invoke(cli, ['init', 'test-project'])
self.assertEqual(result.exit_code, 0)

missing_file = Path('test-project/src/script.py')
if missing_file.exists():
missing_file.unlink()

result = self.runner.invoke(cli, ['validate', 'test-project/workflow.graphml'])
self.assertNotEqual(result.exit_code, 0)
self.assertIn('Missing source file', result.output)

def test_status_command(self):
result = self.runner.invoke(cli, ['status'])
Expand Down