Skip to content

VZ print error message#79

Open
vickimzhang wants to merge 6 commits intomainfrom
vz-print-error-message
Open

VZ print error message#79
vickimzhang wants to merge 6 commits intomainfrom
vz-print-error-message

Conversation

@vickimzhang
Copy link
Copy Markdown
Collaborator

@vickimzhang vickimzhang commented Mar 18, 2026

  • failure message now prints even if run fails
  • failed runs still returns result scenario
  • add handling when error message is regarding signing into SS or if there is an issue with the license

Summary by CodeRabbit

  • Bug Fixes

    • Improved error handling for scenario runs with normalized, user-friendly messages for common failures (e.g., authentication and license issues) and clearer diagnostics when executions fail.
    • Ensures failures are surfaced as explicit errors when runs produce no results.
  • Tests

    • Added and updated tests to verify run-failure behavior and ensure failures raise the expected errors.

@vickimzhang vickimzhang requested a review from katieb1 March 18, 2026 20:11
@katieb1 katieb1 marked this pull request as draft March 26, 2026 16:14
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 1, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d520c2d3-0ac9-4a3a-bebc-8846e767c48b

📥 Commits

Reviewing files that changed from the base of the PR and between 1a1f0e9 and d2fcd3c.

📒 Files selected for processing (1)
  • tests/test_pysyncrosim.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_pysyncrosim.py

📝 Walkthrough

Walkthrough

The Scenario.run() method was changed to track failures via an error_msg, map certain RuntimeError message substrings to normalized messages, rely on exceptions from console calls instead of return-code checks, and raise RuntimeError with either the results scenario ID or the current scenario ID depending on outcome. Tests were updated and a repository path corrected.

Changes

Cohort / File(s) Summary
Error Handling Refactor
pysyncrosim/scenario.py
Reworked Scenario.run() to use a local error_msg, remove return-code inspection, map specific RuntimeError message substrings to normalized user-facing errors (e.g., "not signed in", "SyncroSim license file"), clear cached state, compute results scenario ID, and raise RuntimeError with appropriate scenario ID and error message when failures occur.
Test Adjustments
tests/test_pysyncrosim.py
Updated hardcoded repo path from C:/Users/VickiZhang/Documents/GH_ApexRMS to C:/GH_ApexRMS, added test_scenario_run_failure expecting RuntimeError on failed runs, and changed test_scenario_copy_dep_delete to assert a RuntimeError ("Run failed for Scenario") instead of expecting None.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 I hopped the code paths, twitching my nose,

found errors mapped where mystery grows,
No silent exits — they now declare,
Scenario IDs and messages laid bare,
A rabbit cheers for clear error prose.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'VZ print error message' is vague and does not clearly convey the actual changes, which involve improving error handling in the Scenario.run() method and changing how failures are reported. Use a more descriptive title that explains the main change, such as 'Improve error message handling in Scenario.run() for failed runs' or 'Print error messages and return results on run failure'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch vz-print-error-message

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@vickimzhang vickimzhang marked this pull request as ready for review April 1, 2026 14:20
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
pysyncrosim/scenario.py (1)

623-654: Consider restructuring to avoid return inside finally.

The static analysis tool flags the return result_scn on line 650 inside the finally block (Ruff B012). While your current logic is correct (the return only executes when error_msg is None), having return inside finally is unconventional and can be confusing for future maintainers.

Consider moving the success-path logic outside the finally block by using a result variable that gets returned after the try/except/finally.

♻️ Suggested refactor to move return outside finally
     def run(self, copy_external_inputs=False):
         # ... docstring ...
         args = ["--run", "--lib=%s" % self.library.location,
                 "--sid=%d" % self.__sid]
         
         if copy_external_inputs is True:
             args += ["--copyextfiles=yes"]
         
         error_msg = None
+        result_scn = None
 
         try:
             print(f"Running Scenario [{self.sid}] {self.name}")
             self.library.session._Session__call_console(args)
 
         except RuntimeError as e:
 
             if "You must be signed in" in str(e):
                 error_msg = ("you must be signed in to SyncroSim. "
                               "Use session.sign_in() to sign in.")
 
             elif "There has been an issue with your SyncroSim license file" in str(e):
                 error_msg = "there has been an issue with your SyncroSim license file."
 
             else:
                 error_msg = str(e)
 
         finally:
-
             # Reset Project Scenarios
             self.project._Project__scenarios = None
 
             # Reset results
             self.__results = None
-
+        
+        # Retrieve Results Scenario ID
+        # Also resets scenarios and results info
+        results_df = self.results()
+
+        if (not results_df.empty):
+            result_id = results_df["ScenarioId"].values[-1]
+
+            if error_msg is not None:
+                raise RuntimeError(f"Run failed for Scenario [{result_id}] "
+                                   f"{self.name}: {error_msg}")
+            else:
+                print("Run successful")
+
             # Retrieve Results Scenario ID
-            # Also resets scenarios and results info
-            results_df = self.results()
-
-            if (not results_df.empty):
-
-                result_id = results_df["ScenarioId"].values[-1]
-
-                if error_msg is not None:
-                    raise RuntimeError(f"Run failed for Scenario [{result_id}] "
-                                       f"{self.name}: {error_msg}")
-                else:
-                    print("Run successful")
-
-                # Return Results Scenario
-                result_scn = self.library.scenarios(project=self.project,
-                                                    name=None,
-                                                    sid=result_id)
-
-                return result_scn
-
-            elif error_msg is not None:
-                raise RuntimeError(f"Run failed for Scenario [{self.sid}] "
-                                   f"{self.name}: {error_msg}")
+            result_scn = self.library.scenarios(project=self.project,
+                                                name=None,
+                                                sid=result_id)
+
+        elif error_msg is not None:
+            raise RuntimeError(f"Run failed for Scenario [{self.sid}] "
+                               f"{self.name}: {error_msg}")
+
+        return result_scn
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pysyncrosim/scenario.py` around lines 623 - 654, The finally block currently
returns result_scn (Ruff B012); instead, declare a local variable (e.g.,
final_result = None) before the try, assign final_result = result_scn inside the
finally when successful (preserve resetting self.project._Project__scenarios and
self.__results and the results() call and prints/raises using error_msg), and
move the actual "return final_result" to after the try/except/finally so no
return occurs inside finally; reference the results() call, result_scn
assignment via self.library.scenarios(..., sid=result_id), and the
error_msg/sid/name-based RuntimeError branches when reorganizing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pysyncrosim/scenario.py`:
- Around line 652-654: The function currently falls through (implicit None) when
results_df.empty is True and error_msg is None; update the end-of-run handling
so it returns an explicit Scenario object and warns instead of returning None:
when results_df.empty and error_msg is None, emit a warning (e.g., using
warnings.warn with context including self.sid and self.name) and then return
self (the Scenario) so the return type matches the docstring; adjust the block
surrounding results_df.empty and error_msg checks (referencing results_df.empty,
error_msg, self.sid, self.name, and the Scenario return) to implement this
explicit warning+return.

In `@tests/test_pysyncrosim.py`:
- Around line 15-17: The test uses a hardcoded Windows-only path in
git_repo_path which breaks portability; replace usage in git_repo_path,
lib_path, and lib_backup_path so the repository root is discovered dynamically
(for example read from an environment variable like REPO_ROOT via
os.environ.get('REPO_ROOT') with a sensible fallback) or compute it relative to
the test file using os.path.dirname(__file__) and os.path.join to construct
lib_path and lib_backup_path; update the references in the test to use this
dynamic repo root so the tests run on different machines and CI.

---

Nitpick comments:
In `@pysyncrosim/scenario.py`:
- Around line 623-654: The finally block currently returns result_scn (Ruff
B012); instead, declare a local variable (e.g., final_result = None) before the
try, assign final_result = result_scn inside the finally when successful
(preserve resetting self.project._Project__scenarios and self.__results and the
results() call and prints/raises using error_msg), and move the actual "return
final_result" to after the try/except/finally so no return occurs inside
finally; reference the results() call, result_scn assignment via
self.library.scenarios(..., sid=result_id), and the error_msg/sid/name-based
RuntimeError branches when reorganizing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6a57591d-acf2-479a-b4b1-8b5bcef683aa

📥 Commits

Reviewing files that changed from the base of the PR and between 2646c9a and 1a1f0e9.

📒 Files selected for processing (2)
  • pysyncrosim/scenario.py
  • tests/test_pysyncrosim.py

Comment on lines +652 to +654
elif error_msg is not None:
raise RuntimeError(f"Run failed for Scenario [{self.sid}] "
f"{self.name}: {error_msg}")
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Function may return None implicitly when results are empty and no error occurred.

When results_df.empty is True and error_msg is None, the function exits without an explicit return, implicitly returning None. This doesn't match the docstring which states it returns a Scenario. Consider whether this edge case should print a warning or return something more explicit.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pysyncrosim/scenario.py` around lines 652 - 654, The function currently falls
through (implicit None) when results_df.empty is True and error_msg is None;
update the end-of-run handling so it returns an explicit Scenario object and
warns instead of returning None: when results_df.empty and error_msg is None,
emit a warning (e.g., using warnings.warn with context including self.sid and
self.name) and then return self (the Scenario) so the return type matches the
docstring; adjust the block surrounding results_df.empty and error_msg checks
(referencing results_df.empty, error_msg, self.sid, self.name, and the Scenario
return) to implement this explicit warning+return.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this edge case where no results scenario is created after a run failure should never happen

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vickimzhang, thanks for the clarification! If SyncroSim always produces a result scenario on a successful run, then that implicit None path is indeed unreachable and the current logic is correct. I'll note this for future reviews.


✏️ Learnings added
Learnt from: vickimzhang
Repo: syncrosim/pysyncrosim PR: 79
File: pysyncrosim/scenario.py:652-654
Timestamp: 2026-04-01T14:26:18.541Z
Learning: In `pysyncrosim/scenario.py`, the `Scenario.run()` method's `finally` block handles two branches after checking `results_df`: (1) results exist, and (2) no results with an error. The case where `results_df.empty` is `True` and `error_msg` is `None` (implicit `None` return) is intentionally unreachable — SyncroSim always creates a result scenario on a successful run, so a successful run will never produce an empty results DataFrame.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines +15 to 17
git_repo_path = "C:/GH_ApexRMS"
lib_path = os.path.join(git_repo_path, "pysyncrosim/tests", lib_name)
lib_backup_path = os.path.join(git_repo_path, "pysyncrosim/tests", "spatial-example.ssimbak")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Hardcoded absolute path reduces test portability.

The path C:/GH_ApexRMS is Windows-specific and tied to a particular machine's directory structure. This will cause test failures on other developers' machines or CI environments. Consider using environment variables or a configuration file for the repository path.

🛠️ Suggested fix using environment variable
-git_repo_path = "C:/GH_ApexRMS"
+git_repo_path = os.environ.get("PYSYNCROSIM_TEST_REPO_PATH", "C:/GH_ApexRMS")

Alternatively, use a path relative to the test file:

-git_repo_path = "C:/GH_ApexRMS"
+git_repo_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
git_repo_path = "C:/GH_ApexRMS"
lib_path = os.path.join(git_repo_path, "pysyncrosim/tests", lib_name)
lib_backup_path = os.path.join(git_repo_path, "pysyncrosim/tests", "spatial-example.ssimbak")
git_repo_path = os.environ.get("PYSYNCROSIM_TEST_REPO_PATH", "C:/GH_ApexRMS")
lib_path = os.path.join(git_repo_path, "pysyncrosim/tests", lib_name)
lib_backup_path = os.path.join(git_repo_path, "pysyncrosim/tests", "spatial-example.ssimbak")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_pysyncrosim.py` around lines 15 - 17, The test uses a hardcoded
Windows-only path in git_repo_path which breaks portability; replace usage in
git_repo_path, lib_path, and lib_backup_path so the repository root is
discovered dynamically (for example read from an environment variable like
REPO_ROOT via os.environ.get('REPO_ROOT') with a sensible fallback) or compute
it relative to the test file using os.path.dirname(__file__) and os.path.join to
construct lib_path and lib_backup_path; update the references in the test to use
this dynamic repo root so the tests run on different machines and CI.

@vickimzhang
Copy link
Copy Markdown
Collaborator Author

@katieb1 let me know if I should ask @spearmangillman for a review at this point now!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant