Skip to content

Commit e72710d

Browse files
authored
Report missing or invalid workflow file on launch (#2623)
Launching the editor with a workflow file that does not exist, or that exists but cannot be parsed as a workflow, would open a blank editor with no indication of the problem. The editor now reports that the file is missing or not a valid workflow and still opens, instead of silently starting empty. Launching with no file or with a directory still opens empty as before. The --no-editor player now reports an unparseable file with the same invalid-workflow message instead of a raw parser exception, and its missing-file message names the file. Both messages include the full path so a batch run can identify which file failed and the export-image path also shares the missing-file message. Closes #2615
1 parent 11fd9bc commit e72710d

7 files changed

Lines changed: 127 additions & 14 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.IO;
3+
using Microsoft.VisualStudio.TestTools.UnitTesting;
4+
5+
namespace Bonsai.Editor.Tests
6+
{
7+
[TestClass]
8+
[DoNotParallelize]
9+
public class WorkflowLoadErrorTests
10+
{
11+
static string CreateTempWorkflowFile(string contents)
12+
{
13+
var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".bonsai");
14+
File.WriteAllText(path, contents);
15+
return path;
16+
}
17+
18+
[TestMethod]
19+
public void Run_NonXmlContent_ThrowsInvalidFormatWithInner()
20+
{
21+
var path = CreateTempWorkflowFile("this is not a workflow at all");
22+
try
23+
{
24+
var exception = Assert.ThrowsException<InvalidOperationException>(
25+
() => WorkflowRunner.Run(path, new(), visualizerProvider: null));
26+
Assert.IsNotNull(exception.InnerException);
27+
Assert.IsTrue(WorkflowRunner.IsWorkflowFormatError(exception.InnerException));
28+
StringAssert.Contains(exception.Message, Path.GetFileName(path));
29+
}
30+
finally { File.Delete(path); }
31+
}
32+
33+
[TestMethod]
34+
public void Run_WellFormedXmlWrongSchema_IsNotFormatError()
35+
{
36+
var path = CreateTempWorkflowFile("<NotAWorkflow><Element /></NotAWorkflow>");
37+
try
38+
{
39+
var exception = Assert.ThrowsException<InvalidOperationException>(
40+
() => WorkflowRunner.Run(path, new(), visualizerProvider: null));
41+
Assert.IsFalse(WorkflowRunner.IsWorkflowFormatError(exception));
42+
}
43+
finally { File.Delete(path); }
44+
}
45+
46+
[TestMethod]
47+
public void Run_MissingFile_ThrowsArgumentExceptionWithFileName()
48+
{
49+
var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".bonsai");
50+
var exception = Assert.ThrowsException<ArgumentException>(
51+
() => WorkflowRunner.Run(path, new(), visualizerProvider: null));
52+
StringAssert.Contains(exception.Message, Path.GetFileName(path));
53+
}
54+
}
55+
}

Bonsai.Editor/EditorForm.cs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -312,10 +312,8 @@ protected override async void OnLoad(EventArgs e)
312312
{
313313
RestoreEditorSettings();
314314
var initialFileName = FileName;
315-
var validFileName =
316-
!string.IsNullOrEmpty(initialFileName) &&
317-
Path.GetExtension(initialFileName) == Project.BonsaiExtension &&
318-
File.Exists(initialFileName);
315+
var fileRequested = !string.IsNullOrEmpty(initialFileName);
316+
var fileExists = fileRequested && File.Exists(initialFileName);
319317

320318
Observable.Merge(
321319
InitializeSubjectSourcesAsync(),
@@ -325,7 +323,7 @@ protected override async void OnLoad(EventArgs e)
325323
.Subscribe(formCancellation.Token);
326324

327325
var currentDirectory = Project.GetCurrentBaseDirectory(out bool currentDirectoryRestricted);
328-
var workflowBaseDirectory = validFileName ? Project.GetWorkflowBaseDirectory(initialFileName) : currentDirectory;
326+
var workflowBaseDirectory = fileExists ? Project.GetWorkflowBaseDirectory(initialFileName) : currentDirectory;
329327
if (currentDirectoryRestricted)
330328
{
331329
currentDirectory = workflowBaseDirectory;
@@ -341,7 +339,13 @@ protected override async void OnLoad(EventArgs e)
341339
initialization = InitializeEditorExtensionsAsync(formCancellation.Token);
342340
base.OnLoad(e);
343341

344-
await InitializeWorkflowAsync(validFileName ? initialFileName : default);
342+
if (fileRequested && !fileExists)
343+
{
344+
var fileNotFoundMessage = string.Format(Resources.OpenWorkflow_FileNotFound, Path.GetFileName(initialFileName));
345+
MessageBox.Show(this, fileNotFoundMessage, Resources.OpenWorkflow_Error_Caption, MessageBoxButtons.OK, MessageBoxIcon.Error);
346+
}
347+
348+
await InitializeWorkflowAsync(fileExists ? initialFileName : default);
345349
}
346350

347351
protected override void ScaleControl(SizeF factor, BoundsSpecified specified)
@@ -880,14 +884,23 @@ async Task<bool> OpenWorkflowAsync(string fileName, bool setWorkingDirectory)
880884
try { builderCandidate = ElementStore.LoadWorkflow(fileName, out workflowVersion); }
881885
catch (SystemException ex) when (ex is InvalidOperationException || ex is XmlException)
882886
{
883-
var activeException = ex.InnerException ?? ex;
884-
var errorMessage = activeException.Message;
885-
if (activeException.InnerException != null)
887+
string errorMessage;
888+
if (WorkflowRunner.IsWorkflowFormatError(ex))
886889
{
887-
errorMessage += Environment.NewLine + activeException.InnerException.Message;
890+
errorMessage = string.Format(Resources.OpenWorkflow_InvalidFormat, Path.GetFileName(fileName));
891+
}
892+
else
893+
{
894+
var activeException = ex.InnerException ?? ex;
895+
errorMessage = activeException.Message;
896+
if (activeException.InnerException != null)
897+
{
898+
errorMessage += Environment.NewLine + activeException.InnerException.Message;
899+
}
900+
901+
errorMessage = string.Format(Resources.OpenWorkflow_Error, Path.GetFileName(fileName), errorMessage);
888902
}
889903

890-
errorMessage = string.Format(Resources.OpenWorkflow_Error, Path.GetFileName(fileName), errorMessage);
891904
MessageBox.Show(this, errorMessage, Resources.OpenWorkflow_Error_Caption, MessageBoxButtons.OK, MessageBoxIcon.Error);
892905
return false;
893906
}

Bonsai.Editor/Properties/Resources.Designer.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,24 @@ internal static string OpenWorkflow_Error_Caption {
493493
return ResourceManager.GetString("OpenWorkflow_Error_Caption", resourceCulture);
494494
}
495495
}
496+
497+
/// <summary>
498+
/// Looks up a localized string similar to The workflow file &apos;{0}&apos; does not exist..
499+
/// </summary>
500+
internal static string OpenWorkflow_FileNotFound {
501+
get {
502+
return ResourceManager.GetString("OpenWorkflow_FileNotFound", resourceCulture);
503+
}
504+
}
505+
506+
/// <summary>
507+
/// Looks up a localized string similar to The file &apos;{0}&apos; is not a valid workflow..
508+
/// </summary>
509+
internal static string OpenWorkflow_InvalidFormat {
510+
get {
511+
return ResourceManager.GetString("OpenWorkflow_InvalidFormat", resourceCulture);
512+
}
513+
}
496514

497515
/// <summary>
498516
/// Looks up a localized string similar to Output.

Bonsai.Editor/Properties/Resources.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,12 @@ Copyright (c) .NET Foundation and Contributors</value>
194194
<data name="OpenWorkflow_Error_Caption" xml:space="preserve">
195195
<value>Open Error</value>
196196
</data>
197+
<data name="OpenWorkflow_FileNotFound" xml:space="preserve">
198+
<value>The workflow file '{0}' does not exist.</value>
199+
</data>
200+
<data name="OpenWorkflow_InvalidFormat" xml:space="preserve">
201+
<value>The file '{0}' is not a valid workflow.</value>
202+
</data>
197203
<data name="PasteFromClipboard_Error" xml:space="preserve">
198204
<value>There was an error pasting the selected elements from the clipboard:
199205
{0}</value>

Bonsai.Editor/WorkflowExporter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public static void ExportImage(string fileName, string imageFileName)
1919

2020
if (!File.Exists(fileName))
2121
{
22-
throw new ArgumentException("Specified workflow file does not exist.", nameof(fileName));
22+
throw new ArgumentException(string.Format(Resources.OpenWorkflow_FileNotFound, fileName), nameof(fileName));
2323
}
2424

2525
if (string.IsNullOrEmpty(imageFileName))

Bonsai.Editor/WorkflowRunner.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Bonsai.Design;
22
using Bonsai.Editor.GraphModel;
3+
using Bonsai.Editor.Properties;
34
using Bonsai.Expressions;
45
using System;
56
using System.Collections.Generic;
@@ -169,10 +170,17 @@ public static void Run(
169170

170171
if (!File.Exists(fileName))
171172
{
172-
throw new ArgumentException("Specified workflow file does not exist.", nameof(fileName));
173+
throw new ArgumentException(string.Format(Resources.OpenWorkflow_FileNotFound, fileName), nameof(fileName));
174+
}
175+
176+
WorkflowBuilder workflowBuilder;
177+
try { workflowBuilder = ElementStore.LoadWorkflow(fileName); }
178+
catch (Exception ex) when (IsWorkflowFormatError(ex))
179+
{
180+
throw new InvalidOperationException(
181+
string.Format(Resources.OpenWorkflow_InvalidFormat, fileName), ex);
173182
}
174183

175-
var workflowBuilder = ElementStore.LoadWorkflow(fileName);
176184
var settingsPath = Project.GetWorkflowSettingsDirectory(fileName);
177185
layoutPath ??= LayoutHelper.GetCompatibleLayoutPath(settingsPath, fileName);
178186

@@ -198,6 +206,17 @@ public static void Run(
198206
}
199207
}
200208

209+
internal static bool IsWorkflowFormatError(Exception exception)
210+
{
211+
for (var error = exception; error != null; error = error.InnerException)
212+
{
213+
if (error is XmlException)
214+
return true;
215+
}
216+
217+
return false;
218+
}
219+
201220
static void LogWorkflowExceptionStackTrace(
202221
string fileName,
203222
WorkflowBuilder workflowBuilder,

Bonsai/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,8 @@ internal static int Main(string[] args)
341341
debugScripts = editorFlags.HasFlag(EditorFlags.DebugScripts);
342342
updatePackages = editorFlags.HasFlag(EditorFlags.UpdatesAvailable);
343343
initialFileName = AppResult.GetResult<string>();
344+
if (!string.IsNullOrEmpty(initialFileName) && !File.Exists(initialFileName))
345+
initialFileName = null;
344346
launchResult = (EditorResult)AppResult.GetResult<int>();
345347
}
346348
}

0 commit comments

Comments
 (0)