Skip to content

Commit 2ae0d8e

Browse files
authored
Fixes #2402 Load simulations from a MoBi snapshot (#2403)
* Fixes #2402 Load simulations from a MoBi snapshot * Unwrap async exceptions with GetAwaiter().GetResult() * Document case-sensitive name matching in LoadSimulationsFromSnapshot * Use unique temp folders for SnapshotTask integration spec
1 parent 6a74f0e commit 2ae0d8e

6 files changed

Lines changed: 1060 additions & 0 deletions

File tree

src/MoBi.R/Api.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public static void InitializeOnce(ApiConfig apiConfig)
4646

4747
public static IPassiveTransportsTask GetPassiveTransportsTask() => resolveTask<IPassiveTransportsTask>();
4848

49+
public static ISnapshotTask GetSnapshotTask() => resolveTask<ISnapshotTask>();
50+
4951
private static T resolveTask<T>()
5052
{
5153
try
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
using System;
2+
using System.Linq;
3+
using OSPSuite.CLI.Core.RunOptions;
4+
using OSPSuite.CLI.Core.Services;
5+
using MoBiSimulation = MoBi.R.Domain.MoBiSimulation;
6+
using CoreSnapshotTask = MoBi.Core.Snapshots.Services.ISnapshotTask;
7+
8+
namespace MoBi.R.Services
9+
{
10+
public interface ISnapshotTask
11+
{
12+
/// <summary>
13+
/// Loads the simulations stored in the snapshot file at <paramref name="snapshotFile" />. If
14+
/// <paramref name="simulationNames" /> is provided, only the simulations whose names match are returned.
15+
/// Matching is ordinal and case-sensitive, consistent with the rest of the OSP suite's name lookups.
16+
/// When no name is supplied, every simulation contained in the snapshot is returned (equivalent to opening the
17+
/// snapshot in MoBi).
18+
/// </summary>
19+
MoBiSimulation[] LoadSimulationsFromSnapshot(string snapshotFile, params string[] simulationNames);
20+
21+
/// <summary>
22+
/// Runs the snapshot workflow for the given <paramref name="inputFolder" /> and <paramref name="outputFolder" />
23+
/// using sensible defaults for the remaining options. Optional <paramref name="folders" /> can be provided to
24+
/// process each folder pair instead of the single input/output pair.
25+
/// </summary>
26+
void RunSnapshot(string inputFolder, string outputFolder, bool runSimulations = true,
27+
SnapshotExportMode exportMode = SnapshotExportMode.Snapshot, params string[] folders);
28+
}
29+
30+
public class SnapshotTask : ISnapshotTask
31+
{
32+
private readonly CoreSnapshotTask _snapshotTask;
33+
private readonly IBatchRunner<SnapshotRunOptions> _snapshotRunner;
34+
35+
public SnapshotTask(CoreSnapshotTask snapshotTask, IBatchRunner<SnapshotRunOptions> snapshotRunner)
36+
{
37+
_snapshotTask = snapshotTask;
38+
_snapshotRunner = snapshotRunner;
39+
}
40+
41+
public MoBiSimulation[] LoadSimulationsFromSnapshot(string snapshotFile, params string[] simulationNames)
42+
{
43+
var project = _snapshotTask.LoadProjectFromSnapshotFileAsync(snapshotFile).GetAwaiter().GetResult();
44+
if (project == null)
45+
return Array.Empty<MoBiSimulation>();
46+
47+
var allSimulations = project.Simulations;
48+
49+
var matched = simulationNames.Length == 0
50+
? allSimulations
51+
: allSimulations.Where(x => simulationNames.Contains(x.Name));
52+
53+
return matched.Select(x => new MoBiSimulation(x)).ToArray();
54+
}
55+
56+
public void RunSnapshot(string inputFolder, string outputFolder, bool runSimulations = true,
57+
SnapshotExportMode exportMode = SnapshotExportMode.Snapshot, params string[] folders)
58+
{
59+
var runOptions = new SnapshotRunOptions
60+
{
61+
InputFolder = inputFolder,
62+
OutputFolder = outputFolder,
63+
RunSimulations = runSimulations,
64+
ExportMode = exportMode,
65+
Folders = folders
66+
};
67+
68+
_snapshotRunner.RunBatchAsync(runOptions).GetAwaiter().GetResult();
69+
}
70+
}
71+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using OSPSuite.Core.Services;
2+
3+
namespace MoBi.R.Services
4+
{
5+
/// <summary>
6+
/// Minimal <see cref="IStartableProcessFactory" /> for the MoBi.R container. The real OSPSuite.Core registration
7+
/// relies on Castle Windsor's typed-factory feature, which the Autofac container used by MoBi.R does not synthesize.
8+
/// Providing an explicit implementation lets <c>PKSimStarter</c> (and therefore the snapshot mappers) resolve so
9+
/// snapshots that contain PK-Sim modules can be re-built in R workflows.
10+
/// </summary>
11+
public class StartableProcessFactory : IStartableProcessFactory
12+
{
13+
public StartableProcess CreateStartableProcess(string applicationPath, params string[] arguments) =>
14+
new StartableProcess(applicationPath, arguments);
15+
}
16+
}

tests/MoBi.R.Tests/Data/snapshot_no_pksim_modules.json

Lines changed: 848 additions & 0 deletions
Large diffs are not rendered by default.

tests/MoBi.R.Tests/MoBi.R.Tests.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@
5757
<None Update="Data\simulation with two modules.pkml">
5858
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
5959
</None>
60+
<None Update="Data\snapshot_no_pksim_modules.json">
61+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
62+
</None>
6063
<None Update="OSPSuite.Dimensions.xml">
6164
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
6265
</None>
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
using System;
2+
using System.IO;
3+
using System.Linq;
4+
using OSPSuite.BDDHelper;
5+
using OSPSuite.BDDHelper.Extensions;
6+
using OSPSuite.CLI.Core.Services;
7+
using OSPSuite.Utility;
8+
using static MoBi.R.Tests.HelperForSpecs;
9+
using MoBiSimulation = MoBi.R.Domain.MoBiSimulation;
10+
using ISnapshotTask = MoBi.R.Services.ISnapshotTask;
11+
12+
namespace MoBi.R.Tests.Services
13+
{
14+
internal abstract class concern_for_SnapshotTask : ContextForIntegration<ISnapshotTask>
15+
{
16+
protected string _snapshotFile;
17+
18+
public override void GlobalContext()
19+
{
20+
base.GlobalContext();
21+
sut = Api.GetSnapshotTask();
22+
}
23+
24+
protected override void Context()
25+
{
26+
base.Context();
27+
_snapshotFile = DataTestFileFullPath("snapshot_no_pksim_modules.json");
28+
}
29+
}
30+
31+
internal class When_loading_all_simulations_from_a_snapshot : concern_for_SnapshotTask
32+
{
33+
private MoBiSimulation[] _simulations;
34+
35+
protected override void Because()
36+
{
37+
_simulations = sut.LoadSimulationsFromSnapshot(_snapshotFile);
38+
}
39+
40+
[Observation]
41+
public void should_return_every_simulation_defined_in_the_snapshot()
42+
{
43+
_simulations.ShouldNotBeNull();
44+
_simulations.Length.ShouldBeGreaterThan(0);
45+
}
46+
}
47+
48+
internal class When_loading_simulations_filtered_by_an_existing_simulation_name : concern_for_SnapshotTask
49+
{
50+
private MoBiSimulation[] _simulations;
51+
private string _existingSimulationName;
52+
53+
protected override void Context()
54+
{
55+
base.Context();
56+
_existingSimulationName = sut.LoadSimulationsFromSnapshot(_snapshotFile).First().Name;
57+
}
58+
59+
protected override void Because()
60+
{
61+
_simulations = sut.LoadSimulationsFromSnapshot(_snapshotFile, _existingSimulationName);
62+
}
63+
64+
[Observation]
65+
public void should_return_only_simulations_whose_name_matches()
66+
{
67+
_simulations.Length.ShouldBeEqualTo(1);
68+
_simulations[0].Name.ShouldBeEqualTo(_existingSimulationName);
69+
}
70+
}
71+
72+
internal class When_loading_simulations_filtered_by_a_name_that_does_not_exist : concern_for_SnapshotTask
73+
{
74+
private MoBiSimulation[] _simulations;
75+
76+
protected override void Because()
77+
{
78+
_simulations = sut.LoadSimulationsFromSnapshot(_snapshotFile, "ThisSimulationDoesNotExist");
79+
}
80+
81+
[Observation]
82+
public void should_return_an_empty_array()
83+
{
84+
_simulations.Length.ShouldBeEqualTo(0);
85+
}
86+
}
87+
88+
internal class When_running_a_snapshot_export_using_the_convenient_method : concern_for_SnapshotTask
89+
{
90+
private readonly string _inputFolder = Path.Combine(Path.GetTempPath(), $"MoBi_SnapshotTask_Input_{Guid.NewGuid():N}");
91+
private readonly string _outputFolder = Path.Combine(Path.GetTempPath(), $"MoBi_SnapshotTask_Output_{Guid.NewGuid():N}");
92+
private readonly string _snapshotFileName = "snapshot_no_pksim_modules.json";
93+
94+
protected override void Context()
95+
{
96+
base.Context();
97+
DirectoryHelper.CreateDirectory(_inputFolder);
98+
FileHelper.Copy(_snapshotFile, Path.Combine(_inputFolder, _snapshotFileName));
99+
}
100+
101+
protected override void Because()
102+
{
103+
sut.RunSnapshot(_inputFolder, _outputFolder, exportMode: SnapshotExportMode.Project);
104+
}
105+
106+
[Observation]
107+
public void should_write_the_mbp3_project_file_to_the_output_folder()
108+
{
109+
Directory.Exists(_outputFolder).ShouldBeTrue();
110+
File.Exists(Path.Combine(_outputFolder, "snapshot_no_pksim_modules.mbp3")).ShouldBeTrue();
111+
}
112+
113+
public override void Cleanup()
114+
{
115+
base.Cleanup();
116+
DirectoryHelper.DeleteDirectory(_outputFolder, true);
117+
DirectoryHelper.DeleteDirectory(_inputFolder, true);
118+
}
119+
}
120+
}

0 commit comments

Comments
 (0)