From 9aa827a4dbce6d9ca627142b08dfd4de33a21af6 Mon Sep 17 00:00:00 2001 From: bhuvan-somisetty Date: Sat, 26 Sep 2026 09:00:03 +0530 Subject: [PATCH] fix(cli): exit non-zero when inspect or watch fails --- concore_cli/cli.py | 8 ++++++-- concore_cli/commands/inspect.py | 13 ++++++++++--- concore_cli/commands/watch.py | 8 +++++--- tests/test_cli.py | 16 ++++++++++++++++ 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/concore_cli/cli.py b/concore_cli/cli.py index 4b376c9..8cef537 100644 --- a/concore_cli/cli.py +++ b/concore_cli/cli.py @@ -134,7 +134,9 @@ def validate(workflow_file, source, output_format): def inspect(workflow_file, source, output_json): """Inspect a workflow file and show its structure""" try: - inspect_workflow(workflow_file, source, output_json, console) + ok = inspect_workflow(workflow_file, source, output_json, console) + if not ok: + sys.exit(1) except Exception as e: console.print(f"[red]Error:[/red] {str(e)}") sys.exit(1) @@ -168,7 +170,9 @@ def stop(): def watch(study_dir, interval, once): """Watch a running simulation study for live monitoring""" try: - watch_study(study_dir, interval, once, console) + ok = watch_study(study_dir, interval, once, console) + if not ok: + sys.exit(1) except Exception as e: console.print(f"[red]Error:[/red] {str(e)}") sys.exit(1) diff --git a/concore_cli/commands/inspect.py b/concore_cli/commands/inspect.py index 0dce24a..4d960f4 100644 --- a/concore_cli/commands/inspect.py +++ b/concore_cli/commands/inspect.py @@ -12,7 +12,7 @@ def inspect_workflow(workflow_file, source_dir, output_json, console): if output_json: return _inspect_json(workflow_path, source_dir) - _inspect_rich(workflow_path, source_dir, console) + return _inspect_rich(workflow_path, source_dir, console) def _inspect_rich(workflow_path, source_dir, console): @@ -28,7 +28,7 @@ def _inspect_rich(workflow_path, source_dir, console): if not soup.find("graphml"): console.print("[red]Not a valid GraphML file[/red]") - return + return False nodes = soup.find_all("node") edges = soup.find_all("edge") @@ -172,8 +172,12 @@ def _inspect_rich(workflow_path, source_dir, console): except FileNotFoundError: console.print(f"[red]File not found:[/red] {workflow_path}") + return False except Exception as e: console.print(f"[red]Inspection failed:[/red] {str(e)}") + return False + + return True def _inspect_json(workflow_path, source_dir): @@ -187,7 +191,7 @@ def _inspect_json(workflow_path, source_dir): if not soup.find("graphml"): print(json.dumps({"error": "Not a valid GraphML file"}, indent=2)) - return + return False nodes = soup.find_all("node") edges = soup.find_all("edge") @@ -269,3 +273,6 @@ def _inspect_json(workflow_path, source_dir): except Exception as e: print(json.dumps({"error": str(e)}, indent=2)) + return False + + return True diff --git a/concore_cli/commands/watch.py b/concore_cli/commands/watch.py index e82efba..9b20164 100644 --- a/concore_cli/commands/watch.py +++ b/concore_cli/commands/watch.py @@ -14,7 +14,7 @@ def watch_study(study_dir, interval, once, console): study_path = Path(study_dir).resolve() if not study_path.is_dir(): console.print(f"[red]Error:[/red] '{study_dir}' is not a directory") - return + return False nodes = _find_nodes(study_path) edges = _find_edges(study_path, nodes) @@ -28,12 +28,12 @@ def watch_study(study_dir, interval, once, console): border_style="yellow", ) ) - return + return True if once: output = _build_display(study_path, nodes, edges) console.print(output) - return + return True console.print(f"[cyan]Watching:[/cyan] {study_path}") console.print(f"[dim]Refresh every {interval}s — Ctrl+C to stop[/dim]\n") @@ -48,6 +48,8 @@ def watch_study(study_dir, interval, once, console): except KeyboardInterrupt: console.print("\n[yellow]Watch stopped.[/yellow]") + return True + def _build_display(study_path, nodes, edges): parts = [] diff --git a/tests/test_cli.py b/tests/test_cli.py index d746040..75fa832 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -786,6 +786,22 @@ def test_inspect_missing_source_file(self): self.assertEqual(result.exit_code, 0) self.assertIn("Missing files", result.output) + def test_inspect_invalid_graphml_exits_nonzero(self): + with self.runner.isolated_filesystem(temp_dir=self.temp_dir): + Path("notes.txt").write_text("not graphml") + for args in (["inspect", "notes.txt"], ["inspect", "notes.txt", "--json"]): + with self.subTest(args=args): + result = self.runner.invoke(cli, args) + self.assertEqual(result.exit_code, 1) + self.assertIn("Not a valid GraphML file", result.output) + + def test_watch_file_instead_of_dir_exits_nonzero(self): + with self.runner.isolated_filesystem(temp_dir=self.temp_dir): + Path("notes.txt").write_text("hi") + result = self.runner.invoke(cli, ["watch", "notes.txt", "--once"]) + self.assertEqual(result.exit_code, 1) + self.assertIn("is not a directory", result.output) + if __name__ == "__main__": unittest.main()