mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-11-01 20:34:57 +01:00
Keep scenery files intact on save and follow them on disk
Saves keep comments and every byte they do not change. Faults in a scenery's text are found and described. Files that change while the starter runs are picked up without a manual reload.
This commit is contained in:
16
StarterNG.Tests/Fakes/TestScenery.cs
Normal file
16
StarterNG.Tests/Fakes/TestScenery.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using StarterNG.Application.Abstractions;
|
||||
using StarterNG.Infrastructure.Adapters;
|
||||
using StarterNG.Infrastructure.Sceneries;
|
||||
|
||||
namespace StarterNG.Tests.Fakes;
|
||||
|
||||
/// <summary>The scenery reader over a fake clock and dice, for tests of what it makes of a text.</summary>
|
||||
public static class TestScenery
|
||||
{
|
||||
/// <param name="now">The hour a scenery that names none starts at.</param>
|
||||
public static SceneryParser Parser(DateTime? now = null) =>
|
||||
new(new FixedClock(now ?? new DateTime(2026, 1, 1, 12, 0, 0)), new StubRandom());
|
||||
|
||||
public static SceneryRepository Repository(IFileSystem files) =>
|
||||
new(files, new GamePaths("game"), TestLog.Over(files), Parser());
|
||||
}
|
||||
@@ -72,4 +72,45 @@ public class GameLibraryTests
|
||||
|
||||
Assert.Contains("textures.txt", installation.Log.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_folder_of_the_rolling_stock_that_cannot_be_listed_costs_only_itself()
|
||||
{
|
||||
var files = new InMemoryFileSystem()
|
||||
.WithFile(TestInstallation.At("dynamic", "aaa", "ep09", "textures.txt"), "ep09-1.mat=ep09.e3d,ep09")
|
||||
.WithFile(TestInstallation.At("dynamic", "aaa", "ep09", "ep09.fiz"), "Param: M=2")
|
||||
.WithFile(TestInstallation.At("dynamic", "pkp", "ep07", "textures.txt"), "ep07-135.mat=ep07.e3d,ep07")
|
||||
.WithFile(TestInstallation.At("dynamic", "pkp", "ep07", "ep07.fiz"), "Param: M=1")
|
||||
.WithFile(TestInstallation.At("scenery", "kato.scn"), "//$n Katowice")
|
||||
.DenyListing(TestInstallation.At("dynamic", "aaa"));
|
||||
var installation = new TestInstallation(files);
|
||||
|
||||
installation.Library.Load();
|
||||
|
||||
Assert.True(installation.Library.Loaded);
|
||||
Assert.Equal("ep07-135", Assert.Single(installation.Library.Vehicles.Textures).Skinfile);
|
||||
Assert.Equal("Katowice", Assert.Single(installation.Library.Sceneries).Name);
|
||||
Assert.Contains("aaa", installation.Log.Text);
|
||||
Assert.Equal(1, installation.Physics.For("pkp/ep07/", "ep07")!.Mass);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_whole_rolling_stock_failing_still_leaves_the_sceneries()
|
||||
{
|
||||
var installation = Complete();
|
||||
var library = new GameLibrary(new FailingVehicles(), installation.Sceneries, installation.MiniTextures,
|
||||
installation.Physics, installation.Log);
|
||||
|
||||
library.Load();
|
||||
|
||||
Assert.True(library.Loaded);
|
||||
Assert.Equal("Katowice", Assert.Single(library.Sceneries).Name);
|
||||
Assert.Contains("no disk today", installation.Log.Text);
|
||||
}
|
||||
|
||||
private sealed class FailingVehicles : StarterNG.Application.Abstractions.IVehicleRepository
|
||||
{
|
||||
public int Load(StarterNG.Domain.Vehicles.VehicleCatalog catalog) =>
|
||||
throw new IOException("no disk today");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
using StarterNG.Infrastructure.Sceneries;
|
||||
using StarterNG.Tests.Fakes;
|
||||
|
||||
namespace StarterNG.Tests;
|
||||
|
||||
public class SceneryExportTests
|
||||
{
|
||||
private static SceneryParser Parser() =>
|
||||
new(new FixedClock(new DateTime(2026, 1, 1, 12, 0, 0)), new StubRandom());
|
||||
|
||||
[Fact]
|
||||
public void An_untouched_scenery_exports_its_trainsets_back_verbatim()
|
||||
{
|
||||
@@ -18,7 +14,7 @@ public class SceneryExportTests
|
||||
endtrainset
|
||||
FirstInit
|
||||
""";
|
||||
var scenery = Parser().Parse("scenery/x.scn", scn);
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
string exported = scenery.BuildExportContent(skipDecorTrainsets: false, new StubRandom());
|
||||
|
||||
@@ -27,6 +23,45 @@ public class SceneryExportTests
|
||||
Assert.Contains("endtrainset", exported);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_consist_emptied_stays_out_of_the_export()
|
||||
{
|
||||
const string scn = """
|
||||
//$n Test
|
||||
trainset pociag tor_1 10 0
|
||||
node -1 -1 ep07-001 dynamic PKP/EP07 ep07 ep07.mmd 0 headdriver 0
|
||||
endtrainset
|
||||
FirstInit
|
||||
""";
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
scenery.Trainsets[0].RemoveVehicles();
|
||||
string exported = scenery.BuildExportContent(skipDecorTrainsets: false, new StubRandom());
|
||||
|
||||
Assert.DoesNotContain("trainset pociag", exported);
|
||||
Assert.DoesNotContain("ep07-001", exported);
|
||||
Assert.Contains("FirstInit", exported);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void An_entry_that_could_not_be_read_goes_when_emptied_too()
|
||||
{
|
||||
const string scn = """
|
||||
//$n Test
|
||||
trainset pociag tor_1 10 0
|
||||
node -1 -1 ep07-001 dynamic PKP/EP07 ep07 ep07.mmd 0 headdriver 0
|
||||
endtrainset
|
||||
FirstInit
|
||||
""";
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
scenery.Trainsets[0].Parsed = false;
|
||||
|
||||
scenery.Trainsets[0].RemoveVehicles();
|
||||
string exported = scenery.BuildExportContent(skipDecorTrainsets: false, new StubRandom());
|
||||
|
||||
Assert.DoesNotContain("ep07-001", exported);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Selected_optional_includes_are_written_back_before_FirstInit()
|
||||
{
|
||||
@@ -36,7 +71,7 @@ public class SceneryExportTests
|
||||
endoptional
|
||||
FirstInit
|
||||
""";
|
||||
var scenery = Parser().Parse("scenery/x.scn", scn);
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
string exported = scenery.BuildExportContent(skipDecorTrainsets: false, new StubRandom());
|
||||
|
||||
@@ -54,7 +89,7 @@ public class SceneryExportTests
|
||||
endoptional
|
||||
FirstInit
|
||||
""";
|
||||
var scenery = Parser().Parse("scenery/x.scn", scn);
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
scenery.Includes[0].Selected = false;
|
||||
|
||||
string exported = scenery.BuildExportContent(skipDecorTrainsets: false, new StubRandom());
|
||||
@@ -71,7 +106,7 @@ public class SceneryExportTests
|
||||
endoptional
|
||||
FirstInit
|
||||
""";
|
||||
var scenery = Parser().Parse("scenery/x.scn", scn);
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
scenery.HasCompanionTimetable = false;
|
||||
Assert.Contains("include rozklad.inc",
|
||||
@@ -95,7 +130,7 @@ public class SceneryExportTests
|
||||
time 8:30 0 0 endtime
|
||||
FirstInit
|
||||
""";
|
||||
var scenery = Parser().Parse("scenery/x.scn", scn);
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
scenery.Weather.Day = 200;
|
||||
scenery.Weather.Temperature = 25;
|
||||
@@ -120,7 +155,7 @@ public class SceneryExportTests
|
||||
endtrainset
|
||||
FirstInit
|
||||
""";
|
||||
var scenery = Parser().Parse("scenery/x.scn", scn);
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
Assert.True(scenery.Trainsets[0].Decor);
|
||||
Assert.DoesNotContain("trainset ai_1",
|
||||
@@ -136,11 +171,28 @@ public class SceneryExportTests
|
||||
node -1 -1 wagon_1 dynamic PKP/111A 111a 111a.mmd none 0 nobody 0 enddynamic
|
||||
FirstInit
|
||||
""";
|
||||
var scenery = Parser().Parse("scenery/x.scn", scn);
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
string exported = scenery.BuildExportContent(skipDecorTrainsets: false, new StubRandom());
|
||||
|
||||
Assert.Contains("wagon_1", exported);
|
||||
Assert.Contains("enddynamic", exported);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, "3 0 enddynamic")]
|
||||
[InlineData(3, "3 3 pantstate enddynamic")]
|
||||
public void A_load_type_is_written_only_after_a_count_above_zero(int count, string tail)
|
||||
{
|
||||
// After a 0 the simulator would read the type as a destination and take
|
||||
// the next vehicle with it.
|
||||
var car = new StarterNG.Classes.Dynamic
|
||||
{
|
||||
Name = "en57", DataFolder = "pkp/en57", SkinFile = "s", MmdFile = "m",
|
||||
LoadType = StarterNG.Classes.Dynamic.PantState, LoadCount = count
|
||||
};
|
||||
car.Coupling.Flags = 3;
|
||||
|
||||
Assert.EndsWith(tail + "\n", car.ToTrainsetNode());
|
||||
}
|
||||
}
|
||||
|
||||
77
StarterNG.Tests/SceneryFaultTextTests.cs
Normal file
77
StarterNG.Tests/SceneryFaultTextTests.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using StarterNG.Domain.Sceneries;
|
||||
|
||||
namespace StarterNG.Tests;
|
||||
|
||||
public class SceneryFaultTextTests
|
||||
{
|
||||
private static string Wording(string key) => key switch
|
||||
{
|
||||
"SyntaxLost" => "lost: {0}",
|
||||
"SyntaxUnmade" => "unmade: {0}",
|
||||
"SyntaxMore" => "and {0} more",
|
||||
_ => key + " {0} {1}"
|
||||
};
|
||||
|
||||
private static string Format(int limit, params SceneryFault[] faults) =>
|
||||
SceneryFaultText.Format(faults, limit, Wording);
|
||||
|
||||
[Fact]
|
||||
public void No_faults_read_as_nothing() =>
|
||||
Assert.Equal("", Format(10));
|
||||
|
||||
[Fact]
|
||||
public void The_place_and_the_fault_stand_in_two_columns_as_wide_as_the_longest_place()
|
||||
{
|
||||
string text = Format(10,
|
||||
new SceneryFault(SceneryFaultKind.VehicleIncomplete, "a.scn", 3, "v1", "x"),
|
||||
new SceneryFault(SceneryFaultKind.NotANumber, "long.inc", 120, "v2", "y"));
|
||||
|
||||
Assert.Equal(
|
||||
"a.scn:3 SyntaxVehicleIncomplete v1 x\n" +
|
||||
"long.inc:120 SyntaxNotANumber v2 y",
|
||||
text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void What_a_fault_has_no_subject_or_token_for_is_filled_in()
|
||||
{
|
||||
string text = Format(10, new SceneryFault(SceneryFaultKind.StrayEndTrainset, "a.scn", 3));
|
||||
|
||||
Assert.Equal("a.scn:3 SyntaxStrayEndTrainset ?", text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void What_a_fault_costs_stands_under_it_in_the_second_column()
|
||||
{
|
||||
string text = Format(10,
|
||||
new SceneryFault(SceneryFaultKind.VehicleNotClosed, "a.scn", 3, "v1", "x") { Lost = "v2, v3", Unmade = "b" });
|
||||
|
||||
Assert.Equal(
|
||||
"a.scn:3 SyntaxVehicleNotClosed v1 x\n" +
|
||||
" lost: v2, v3\n" +
|
||||
" unmade: b",
|
||||
text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Past_the_limit_the_faults_are_only_counted_and_do_not_widen_the_column()
|
||||
{
|
||||
string text = Format(1,
|
||||
new SceneryFault(SceneryFaultKind.NotANumber, "a.scn", 3, "v1", "x"),
|
||||
new SceneryFault(SceneryFaultKind.NotANumber, "a-longer-name.scn", 4, "v2", "y"),
|
||||
new SceneryFault(SceneryFaultKind.NotANumber, "a.scn", 5, "v3", "z"));
|
||||
|
||||
Assert.Equal(
|
||||
"a.scn:3 SyntaxNotANumber v1 x\n" +
|
||||
"and 2 more",
|
||||
text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Exactly_as_many_as_the_limit_are_not_counted_as_more()
|
||||
{
|
||||
string text = Format(1, new SceneryFault(SceneryFaultKind.NotANumber, "a.scn", 3, "v1", "x"));
|
||||
|
||||
Assert.DoesNotContain("more", text);
|
||||
}
|
||||
}
|
||||
376
StarterNG.Tests/SceneryFileWatchTests.cs
Normal file
376
StarterNG.Tests/SceneryFileWatchTests.cs
Normal file
@@ -0,0 +1,376 @@
|
||||
using StarterNG.Application;
|
||||
using StarterNG.Domain.Sceneries;
|
||||
using StarterNG.Infrastructure.Adapters;
|
||||
using StarterNG.Tests.Fakes;
|
||||
|
||||
namespace StarterNG.Tests;
|
||||
|
||||
public class SceneryFileWatchTests
|
||||
{
|
||||
private static readonly string Scn = TestInstallation.At("scenery", "kato.scn");
|
||||
private static readonly string Other = TestInstallation.At("scenery", "lk4.scn");
|
||||
private static readonly string Inc = TestInstallation.At("scenery", "auta.inc");
|
||||
|
||||
private const string Text =
|
||||
"//$n Katowice\n" +
|
||||
"FirstInit\n" +
|
||||
"trainset a tor 0 0\n" +
|
||||
"node -1 0 v1 dynamic d s m 0 headdriver 3 0 enddynamic\n" +
|
||||
"endtrainset\n" +
|
||||
"trainset b tor 0 0\n" +
|
||||
"node -1 0 v2 dynamic d s m 0 headdriver 3 0 enddynamic\n" +
|
||||
"endtrainset\n";
|
||||
|
||||
private static (TestInstallation Installation, SceneryFileWatch Watch, AppState State) Rig(InMemoryFileSystem files)
|
||||
{
|
||||
var installation = new TestInstallation(files);
|
||||
installation.Library.ReloadSceneries();
|
||||
var state = new AppState();
|
||||
return (installation, new SceneryFileWatch(files, installation.Sceneries, installation.Library, state), state);
|
||||
}
|
||||
|
||||
private static InMemoryFileSystem Folder() => new InMemoryFileSystem().WithFile(Scn, Text);
|
||||
|
||||
[Fact]
|
||||
public void Files_as_they_were_read_are_no_change()
|
||||
{
|
||||
var (installation, watch, _) = Rig(Folder());
|
||||
|
||||
Assert.Empty(watch.Look(installation.Library.Sceneries));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_file_changed_outside_is_read_anew()
|
||||
{
|
||||
var files = Folder();
|
||||
var (installation, watch, _) = Rig(files);
|
||||
var current = installation.Library.Sceneries.Single();
|
||||
|
||||
files.WriteAllText(Scn, Text.Replace("Katowice", "Katowice Główne"));
|
||||
|
||||
var change = Assert.Single(watch.Look(installation.Library.Sceneries));
|
||||
Assert.Same(current, change.Current);
|
||||
Assert.Equal("Katowice Główne", change.Fresh!.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void An_include_that_changed_or_came_makes_the_scenery_read_anew()
|
||||
{
|
||||
var files = new InMemoryFileSystem().WithFile(Scn, Text + "include auta.inc end\n");
|
||||
var (installation, watch, _) = Rig(files);
|
||||
Assert.Empty(watch.Look(installation.Library.Sceneries));
|
||||
|
||||
files.WriteAllText(Inc, "// auta\n");
|
||||
Assert.Single(watch.Look(installation.Library.Sceneries));
|
||||
|
||||
installation.Library.ReloadSceneries();
|
||||
Assert.Empty(watch.Look(installation.Library.Sceneries));
|
||||
|
||||
files.WriteAllText(Inc, "// inne auta\n");
|
||||
Assert.Single(watch.Look(installation.Library.Sceneries));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_new_file_in_the_folder_comes_as_an_added_scenery()
|
||||
{
|
||||
var files = Folder();
|
||||
var (installation, watch, _) = Rig(files);
|
||||
|
||||
files.WriteAllText(Other, "//$n Lublin\n");
|
||||
|
||||
var change = Assert.Single(watch.Look(installation.Library.Sceneries));
|
||||
Assert.Null(change.Current);
|
||||
Assert.Equal("Lublin", change.Fresh!.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_file_is_gone_only_if_still_away_on_the_next_look()
|
||||
{
|
||||
var files = Folder();
|
||||
var (installation, watch, _) = Rig(files);
|
||||
var current = installation.Library.Sceneries.Single();
|
||||
|
||||
files.DeleteFile(Scn);
|
||||
Assert.Empty(watch.Look(installation.Library.Sceneries));
|
||||
|
||||
var change = Assert.Single(watch.Look(installation.Library.Sceneries));
|
||||
Assert.Same(current, change.Current);
|
||||
Assert.Null(change.Fresh);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_file_replaced_between_two_looks_is_not_gone()
|
||||
{
|
||||
var files = Folder();
|
||||
var (installation, watch, _) = Rig(files);
|
||||
|
||||
files.DeleteFile(Scn);
|
||||
Assert.Empty(watch.Look(installation.Library.Sceneries));
|
||||
|
||||
files.WriteAllText(Scn, Text);
|
||||
Assert.Empty(watch.Look(installation.Library.Sceneries));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_file_that_will_not_load_is_tried_and_logged_once_until_it_can_be()
|
||||
{
|
||||
var files = Folder();
|
||||
var (installation, watch, _) = Rig(files);
|
||||
files.WriteAllText(Other, "//$n Lublin\n");
|
||||
files.FailReading(Other);
|
||||
|
||||
Assert.Empty(watch.Look(installation.Library.Sceneries));
|
||||
string once = installation.Log.Text;
|
||||
Assert.Contains("lk4.scn", once);
|
||||
|
||||
Assert.Empty(watch.Look(installation.Library.Sceneries));
|
||||
Assert.Equal(once, installation.Log.Text);
|
||||
|
||||
files.AllowReading(Other);
|
||||
Assert.Equal("Lublin", Assert.Single(watch.Look(installation.Library.Sceneries)).Fresh!.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Its_own_save_is_no_change_from_outside()
|
||||
{
|
||||
var files = Folder();
|
||||
var (installation, watch, _) = Rig(files);
|
||||
var scenery = installation.Library.Sceneries.Single();
|
||||
var trainset = scenery.Trainsets[0];
|
||||
trainset.ReadyToGo = true;
|
||||
|
||||
var log = new FileDiagnosticsLog(files, installation.Clock, installation.Paths);
|
||||
Assert.Equal(SceneryTrainsetSaveOutcome.Saved, new SaveSceneryTrainset(files, log).Execute(scenery, trainset).Outcome);
|
||||
|
||||
Assert.False(watch.IsStale(scenery));
|
||||
Assert.Empty(watch.Look(installation.Library.Sceneries));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_consist_whose_text_stands_goes_over_with_its_edits()
|
||||
{
|
||||
var files = Folder();
|
||||
var (installation, watch, _) = Rig(files);
|
||||
var current = installation.Library.Sceneries.Single();
|
||||
var edited = current.Trainsets[1];
|
||||
edited.ReadyToGo = true;
|
||||
|
||||
files.WriteAllText(Scn, Text.Replace("Katowice", "Katowice Główne"));
|
||||
var fresh = watch.Look(installation.Library.Sceneries).Single().Fresh!;
|
||||
|
||||
Assert.Empty(SceneryFileWatch.CarryOver(current, fresh));
|
||||
Assert.Same(edited, fresh.Trainsets[1]);
|
||||
Assert.True(SceneryTrainsetText.IsModified(fresh.Trainsets[1]));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void An_edited_consist_whose_text_changed_too_is_the_conflict()
|
||||
{
|
||||
var files = Folder();
|
||||
var (installation, watch, _) = Rig(files);
|
||||
var current = installation.Library.Sceneries.Single();
|
||||
var edited = current.Trainsets[1];
|
||||
edited.ReadyToGo = true;
|
||||
|
||||
files.WriteAllText(Scn, Text.Replace(" v2 ", " v22 "));
|
||||
var fresh = watch.Look(installation.Library.Sceneries).Single().Fresh!;
|
||||
|
||||
Assert.Same(edited, Assert.Single(SceneryFileWatch.CarryOver(current, fresh)));
|
||||
Assert.NotSame(edited, fresh.Trainsets[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_consist_changed_but_not_edited_just_gives_way()
|
||||
{
|
||||
var files = Folder();
|
||||
var (installation, watch, _) = Rig(files);
|
||||
var current = installation.Library.Sceneries.Single();
|
||||
|
||||
files.WriteAllText(Scn, Text.Replace(" v2 ", " v22 "));
|
||||
var fresh = watch.Look(installation.Library.Sceneries).Single().Fresh!;
|
||||
|
||||
Assert.Empty(SceneryFileWatch.CarryOver(current, fresh));
|
||||
Assert.Equal("v22", fresh.Trainsets[1].Vehicles.Single().Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_weather_chosen_by_hand_stays_and_the_authored_one_follows_the_file()
|
||||
{
|
||||
var files = Folder();
|
||||
var (installation, watch, _) = Rig(files);
|
||||
var current = installation.Library.Sceneries.Single();
|
||||
current.Weather.Day = 123;
|
||||
|
||||
files.WriteAllText(Scn, Text.Replace("Katowice", "Katowice Główne"));
|
||||
var fresh = watch.Look(installation.Library.Sceneries).Single().Fresh!;
|
||||
SceneryFileWatch.CarryOver(current, fresh);
|
||||
|
||||
Assert.Equal(123, fresh.Weather.Day);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void What_is_picked_follows_into_the_scenery_read_anew()
|
||||
{
|
||||
var files = Folder();
|
||||
var (installation, watch, state) = Rig(files);
|
||||
var current = installation.Library.Sceneries.Single();
|
||||
var picked = current.Trainsets[1];
|
||||
state.CurrentScenery = current;
|
||||
state.CurrentTrainset = picked;
|
||||
state.StartingVehicleName = "v2";
|
||||
|
||||
files.WriteAllText(Scn, Text.Replace("Katowice", "Katowice Główne"));
|
||||
var change = watch.Look(installation.Library.Sceneries).Single();
|
||||
SceneryFileWatch.CarryOver(change.Current!, change.Fresh!);
|
||||
watch.Apply(change);
|
||||
|
||||
Assert.Same(change.Fresh, installation.Library.Sceneries.Single());
|
||||
Assert.Same(change.Fresh, state.CurrentScenery);
|
||||
Assert.Same(picked, state.CurrentTrainset);
|
||||
Assert.Equal("v2", state.StartingVehicleName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_picked_consist_changed_in_the_file_is_followed_by_its_name()
|
||||
{
|
||||
var files = Folder();
|
||||
var (installation, watch, state) = Rig(files);
|
||||
var current = installation.Library.Sceneries.Single();
|
||||
var picked = current.Trainsets[1];
|
||||
state.CurrentScenery = current;
|
||||
state.CurrentTrainset = picked;
|
||||
state.StartingVehicleName = "v2";
|
||||
|
||||
files.WriteAllText(Scn, Text.Replace(" v2 ", " v22 "));
|
||||
var change = watch.Look(installation.Library.Sceneries).Single();
|
||||
SceneryFileWatch.CarryOver(change.Current!, change.Fresh!);
|
||||
watch.Apply(change);
|
||||
|
||||
Assert.Same(change.Fresh!.Trainsets[1], state.CurrentTrainset);
|
||||
Assert.Null(state.StartingVehicleName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_change_to_a_scenery_reloaded_since_the_look_is_left()
|
||||
{
|
||||
var files = Folder();
|
||||
var (installation, watch, _) = Rig(files);
|
||||
|
||||
files.WriteAllText(Scn, Text.Replace("Katowice", "Katowice Główne"));
|
||||
var change = watch.Look(installation.Library.Sceneries).Single();
|
||||
installation.Library.ReloadSceneries();
|
||||
|
||||
Assert.Equal(SceneryVerdict.Leave, watch.Judge(change).Verdict);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_change_to_a_scenery_saved_from_here_since_the_look_is_left_and_nothing_carried_over()
|
||||
{
|
||||
var files = Folder();
|
||||
var (installation, watch, _) = Rig(files);
|
||||
var current = installation.Library.Sceneries.Single();
|
||||
|
||||
files.WriteAllText(Scn, Text.Replace("Katowice", "Katowice Główne"));
|
||||
var change = watch.Look(installation.Library.Sceneries).Single();
|
||||
current.Sources = change.Fresh!.Sources;
|
||||
|
||||
Assert.Equal(SceneryVerdict.Leave, watch.Judge(change).Verdict);
|
||||
Assert.NotSame(current.Trainsets[0], change.Fresh.Trainsets[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_scenery_new_in_the_folder_is_taken()
|
||||
{
|
||||
var files = Folder();
|
||||
var (installation, watch, _) = Rig(files);
|
||||
|
||||
files.WriteAllText(Other, "//$n Lublin\n");
|
||||
var change = watch.Look(installation.Library.Sceneries).Single();
|
||||
|
||||
Assert.Equal(SceneryVerdict.Take, watch.Judge(change).Verdict);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_scenery_new_in_the_folder_that_got_in_since_the_look_is_left()
|
||||
{
|
||||
var (installation, watch, _) = Rig(Folder());
|
||||
|
||||
var change = new SceneryChange(null, installation.Library.Sceneries.Single());
|
||||
|
||||
Assert.Equal(SceneryVerdict.Leave, watch.Judge(change).Verdict);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_change_that_loses_no_edits_is_taken_with_the_edits_carried_over()
|
||||
{
|
||||
var files = Folder();
|
||||
var (installation, watch, _) = Rig(files);
|
||||
var edited = installation.Library.Sceneries.Single().Trainsets[1];
|
||||
edited.ReadyToGo = true;
|
||||
|
||||
files.WriteAllText(Scn, Text.Replace("Katowice", "Katowice Główne"));
|
||||
var change = watch.Look(installation.Library.Sceneries).Single();
|
||||
|
||||
var judgement = watch.Judge(change);
|
||||
|
||||
Assert.Equal(SceneryVerdict.Take, judgement.Verdict);
|
||||
Assert.Empty(judgement.Lost);
|
||||
Assert.Same(edited, change.Fresh!.Trainsets[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_change_that_loses_edits_is_asked_about_naming_them()
|
||||
{
|
||||
var files = Folder();
|
||||
var (installation, watch, _) = Rig(files);
|
||||
var current = installation.Library.Sceneries.Single();
|
||||
var edited = current.Trainsets[1];
|
||||
edited.ReadyToGo = true;
|
||||
|
||||
files.WriteAllText(Scn, Text.Replace(" v2 ", " v22 "));
|
||||
var change = watch.Look(installation.Library.Sceneries).Single();
|
||||
|
||||
var judgement = watch.Judge(change);
|
||||
|
||||
Assert.Equal(SceneryVerdict.AskFirst, judgement.Verdict);
|
||||
Assert.Same(edited, Assert.Single(judgement.Lost));
|
||||
Assert.Same(current.Trainsets[0], change.Fresh!.Trainsets[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_scenery_gone_with_edits_in_it_stays_for_them_to_be_saved_elsewhere()
|
||||
{
|
||||
var (installation, watch, _) = Rig(Folder());
|
||||
var current = installation.Library.Sceneries.Single();
|
||||
current.Trainsets[1].ReadyToGo = true;
|
||||
|
||||
Assert.Equal(SceneryVerdict.Leave, watch.Judge(new SceneryChange(current, null)).Verdict);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_scenery_gone_with_no_edits_in_it_is_taken_off()
|
||||
{
|
||||
var (installation, watch, _) = Rig(Folder());
|
||||
var current = installation.Library.Sceneries.Single();
|
||||
|
||||
Assert.Equal(SceneryVerdict.Take, watch.Judge(new SceneryChange(current, null)).Verdict);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_scenery_gone_takes_what_was_picked_with_it()
|
||||
{
|
||||
var files = Folder();
|
||||
var (installation, watch, state) = Rig(files);
|
||||
var current = installation.Library.Sceneries.Single();
|
||||
state.CurrentScenery = current;
|
||||
state.CurrentTrainset = current.Trainsets[0];
|
||||
state.StartingVehicleName = "v1";
|
||||
|
||||
watch.Apply(new SceneryChange(current, null));
|
||||
|
||||
Assert.Empty(installation.Library.Sceneries);
|
||||
Assert.Null(state.CurrentScenery);
|
||||
Assert.Null(state.CurrentTrainset);
|
||||
Assert.Null(state.StartingVehicleName);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,10 @@
|
||||
using StarterNG.Infrastructure.Sceneries;
|
||||
using StarterNG.Domain.Sceneries;
|
||||
using StarterNG.Tests.Fakes;
|
||||
|
||||
namespace StarterNG.Tests;
|
||||
|
||||
public class SceneryParserTests
|
||||
{
|
||||
private static SceneryParser Parser(DateTime? now = null) =>
|
||||
new(new FixedClock(now ?? new DateTime(2026, 5, 4, 9, 15, 0)), new StubRandom());
|
||||
|
||||
[Fact]
|
||||
public void Directives_become_the_scenery_headline()
|
||||
{
|
||||
@@ -19,7 +16,7 @@ public class SceneryParserTests
|
||||
//$i images/kato.jpg
|
||||
""";
|
||||
|
||||
var scenery = Parser().Parse("scenery/kato.scn", scn);
|
||||
var scenery = TestScenery.Parser().Parse("scenery/kato.scn", scn);
|
||||
|
||||
Assert.Equal("Śląsk", scenery.Group);
|
||||
Assert.Equal("Osobowy do Katowic", scenery.Name);
|
||||
@@ -32,7 +29,7 @@ public class SceneryParserTests
|
||||
[Fact]
|
||||
public void The_a_directive_marks_a_scenery_archival()
|
||||
{
|
||||
var scenery = Parser().Parse("scenery/old.scn", "//$a\n//$n Stary\n");
|
||||
var scenery = TestScenery.Parser().Parse("scenery/old.scn", "//$a\n//$n Stary\n");
|
||||
|
||||
Assert.True(scenery.Archival);
|
||||
}
|
||||
@@ -46,7 +43,7 @@ public class SceneryParserTests
|
||||
//$f doc/notes.txt
|
||||
""";
|
||||
|
||||
var scenery = Parser().Parse("scenery/x.scn", scn);
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
Assert.Collection(scenery.Attachments,
|
||||
first =>
|
||||
@@ -76,7 +73,7 @@ public class SceneryParserTests
|
||||
FirstInit
|
||||
""";
|
||||
|
||||
var scenery = Parser().Parse("scenery/x.scn", scn);
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
var include = Assert.Single(scenery.Includes);
|
||||
Assert.Equal("zima.inc", include.FilePath);
|
||||
@@ -95,7 +92,7 @@ public class SceneryParserTests
|
||||
endoptional
|
||||
""";
|
||||
|
||||
var include = Assert.Single(Parser().Parse("scenery/x.scn", scn).Includes);
|
||||
var include = Assert.Single(TestScenery.Parser().Parse("scenery/x.scn", scn).Includes);
|
||||
|
||||
Assert.Equal(2, include.Kind);
|
||||
Assert.False(include.Selected);
|
||||
@@ -112,7 +109,7 @@ public class SceneryParserTests
|
||||
FirstInit
|
||||
""";
|
||||
|
||||
var scenery = Parser().Parse("scenery/x.scn", scn);
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
var trainset = Assert.Single(scenery.Trainsets);
|
||||
Assert.True(trainset.Parsed);
|
||||
@@ -126,11 +123,11 @@ public class SceneryParserTests
|
||||
public void Vehicles_outside_a_trainset_are_collected_as_loose()
|
||||
{
|
||||
const string scn = """
|
||||
node -1 -1 wagon_1 dynamic PKP/111A 111a 111a.mmd none 0 nobody 0 enddynamic
|
||||
node -1 -1 wagon_1 dynamic PKP/111A 111a 111a.mmd none 0 nobody 0 0 enddynamic
|
||||
FirstInit
|
||||
""";
|
||||
|
||||
var scenery = Parser().Parse("scenery/x.scn", scn);
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
var vehicle = Assert.Single(scenery.LooseVehicles);
|
||||
Assert.Equal("wagon_1", vehicle.Name);
|
||||
@@ -142,13 +139,43 @@ public class SceneryParserTests
|
||||
[Fact]
|
||||
public void A_malformed_loose_vehicle_is_reported_and_left_in_place()
|
||||
{
|
||||
const string scn = "node x -1 wagon_1 dynamic PKP/111A 111a 111a.mmd none 0 nobody 0 enddynamic\n";
|
||||
const string scn = "node x -1 wagon_1 dynamic PKP/111A 111a 111a.mmd none 0 nobody 0 0 enddynamic\n";
|
||||
|
||||
var scenery = Parser().Parse("scenery/x.scn", scn);
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
Assert.Empty(scenery.LooseVehicles);
|
||||
Assert.Equal(new[] { "wagon_1" }, scenery.LooseVehicleNames);
|
||||
Assert.Contains(scenery.Faults, fault => fault.Contains("wagon_1"));
|
||||
var fault = Assert.Single(scenery.Faults);
|
||||
Assert.Equal((SceneryFaultKind.NotANumber, "wagon_1", "x", 1), (fault.Kind, fault.Subject, fault.Token, fault.Line));
|
||||
Assert.Contains("enddynamic", scenery.Template);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Directives_in_a_file_put_in_place_are_not_the_scenery_s_own()
|
||||
{
|
||||
const string included = """
|
||||
//$n Auta
|
||||
//$l Inne
|
||||
//$d Opis aut
|
||||
//$f doc/auta.pdf Auta
|
||||
//$i auta.jpg
|
||||
//$a
|
||||
trainset auto road1 0 50
|
||||
endtrainset
|
||||
""";
|
||||
string text = SceneryIncludes.Inline("//$d Opis scenerii\n//$f doc/mapa.png Mapa\nFirstInit\ninclude auta.scm end\n",
|
||||
name => (name, included));
|
||||
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", text);
|
||||
|
||||
Assert.Equal("Opis scenerii", scenery.Description);
|
||||
Assert.Equal("doc/mapa.png", Assert.Single(scenery.Attachments).FilePath);
|
||||
Assert.Null(scenery.Name);
|
||||
Assert.Null(scenery.Group);
|
||||
Assert.Null(scenery.ImageName);
|
||||
Assert.False(scenery.Archival);
|
||||
// The file's own trainset is still listed, and its text still written for a start.
|
||||
Assert.Equal("auta.scm", Assert.Single(scenery.Trainsets).SourceFile);
|
||||
Assert.Contains("//$d Opis aut", scenery.BuildExportContent(skipDecorTrainsets: false, new StubRandom()));
|
||||
}
|
||||
}
|
||||
|
||||
382
StarterNG.Tests/ScenerySyntaxTests.cs
Normal file
382
StarterNG.Tests/ScenerySyntaxTests.cs
Normal file
@@ -0,0 +1,382 @@
|
||||
using StarterNG.Domain.Sceneries;
|
||||
using StarterNG.Tests.Fakes;
|
||||
|
||||
namespace StarterNG.Tests;
|
||||
|
||||
public class ScenerySyntaxTests
|
||||
{
|
||||
private static (SceneryFaultKind, string, int, string?, string?) Seen(SceneryFault fault) =>
|
||||
(fault.Kind, fault.File, fault.Line, fault.Subject, fault.Token);
|
||||
|
||||
[Fact]
|
||||
public void A_trainset_commented_out_is_no_trainset()
|
||||
{
|
||||
const string scn = """
|
||||
/* trainset stary tor_1 0 0
|
||||
node -1 0 st dynamic PKP/EP07 ep07 ep07 0 headdriver 3 0 enddynamic
|
||||
endtrainset */
|
||||
// trainset dla gracza:
|
||||
trainset pociag tor_1 10 0
|
||||
node -1 0 ep07-001 dynamic PKP/EP07 ep07 ep07 0 headdriver 3 0 enddynamic//koniec
|
||||
endtrainset
|
||||
""";
|
||||
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
var trainset = Assert.Single(scenery.Trainsets);
|
||||
Assert.Equal("pociag", trainset.Name);
|
||||
Assert.Single(trainset.Vehicles);
|
||||
Assert.Empty(scenery.Faults);
|
||||
Assert.Contains("trainset stary", scenery.Template);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_trainset_without_its_end_is_reported_and_the_next_one_read_on_its_own()
|
||||
{
|
||||
const string scn = """
|
||||
FirstInit
|
||||
trainset pierwszy tor_1 10 0
|
||||
node -1 0 a dynamic PKP/EP07 ep07 ep07 0 headdriver 3 0 enddynamic
|
||||
trainset drugi tor_2 10 0
|
||||
node -1 0 b dynamic PKP/EP07 ep07 ep07 0 headdriver 3 0 enddynamic
|
||||
endtrainset
|
||||
""";
|
||||
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
Assert.Equal((SceneryFaultKind.TrainsetNotClosed, "x.scn", 2, "pierwszy", null),
|
||||
Seen(Assert.Single(scenery.Faults)));
|
||||
var trainset = Assert.Single(scenery.Trainsets);
|
||||
Assert.Equal("drugi", trainset.Name);
|
||||
Assert.Equal("b", Assert.Single(trainset.Vehicles).Name);
|
||||
// The unended one is the simulator's to read as written.
|
||||
Assert.Contains("trainset pierwszy", scenery.Template);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_vehicle_without_its_end_is_reported_yet_its_trainset_listed()
|
||||
{
|
||||
const string scn = """
|
||||
trainset pociag tor_1 10 0
|
||||
node -1 0 a dynamic PKP/EP07 ep07 ep07 0 headdriver 3
|
||||
node -1 0 b dynamic PKP/111A 111a 111a 0 nobody 3 0 enddynamic
|
||||
endtrainset
|
||||
""";
|
||||
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
var fault = Assert.Single(scenery.Faults);
|
||||
Assert.Equal((SceneryFaultKind.VehicleNotClosed, "x.scn", 2, "a", null), Seen(fault));
|
||||
// The simulator goes on to the next enddynamic, taking b for a's.
|
||||
Assert.Equal("b", fault.Lost);
|
||||
Assert.Equal(new[] { "a", "b" }, Assert.Single(scenery.Trainsets).Vehicles.Select(v => v.Name));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_vehicle_with_no_load_count_takes_the_next_one_with_it()
|
||||
{
|
||||
const string scn = """
|
||||
trainset pociag tor_1 10 0
|
||||
node -1 0 a dynamic PKP/EP07 ep07 ep07 0 headdriver 3 enddynamic
|
||||
node -1 0 b dynamic PKP/111A 111a 111a 0 nobody 3 0 enddynamic
|
||||
endtrainset
|
||||
""";
|
||||
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
// Its enddynamic read for the count, "node" for a destination, b's for its end.
|
||||
var fault = Assert.Single(scenery.Faults);
|
||||
Assert.Equal((SceneryFaultKind.LoadCountMissing, 2, "a", "b"), (fault.Kind, fault.Line, fault.Subject, fault.Lost));
|
||||
Assert.Equal(new[] { "a", "b" }, Assert.Single(scenery.Trainsets).Vehicles.Select(v => v.Name));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_load_with_no_type_runs_on_into_the_next_trainset()
|
||||
{
|
||||
const string scn = """
|
||||
trainset pierwszy tor_1 10 0
|
||||
node -1 0 a dynamic PKP/412Z 412z 412z 0 nobody 3 40 enddynamic
|
||||
endtrainset
|
||||
trainset drugi tor_2 10 0
|
||||
node -1 0 b dynamic PKP/EP07 ep07 ep07 0 headdriver 3 0 enddynamic
|
||||
node -1 0 c dynamic PKP/111A 111a 111a 0 nobody 3 0 enddynamic
|
||||
endtrainset
|
||||
""";
|
||||
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
// The second endtrainset is what ends the first trainset then.
|
||||
var fault = Assert.Single(scenery.Faults);
|
||||
Assert.Equal((SceneryFaultKind.LoadTypeMissing, 2, "a", "40", "endtrainset, trainset drugi, b"),
|
||||
(fault.Kind, fault.Line, fault.Subject, fault.Token, fault.Lost));
|
||||
Assert.Equal(new[] { "pierwszy", "drugi" }, scenery.Trainsets.Select(t => t.Name));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("0 none", "none")]
|
||||
[InlineData("0 pantstate", "pantstate")]
|
||||
[InlineData("0 cel Katowice", null)]
|
||||
[InlineData("3 pantstate", null)]
|
||||
[InlineData("12 wegiel", null)]
|
||||
public void One_word_after_the_load_is_taken_for_a_destination_named_enddynamic(string load, string? word)
|
||||
{
|
||||
string scn = $"""
|
||||
trainset pociag tor_1 10 0
|
||||
node -1 0 a dynamic PKP/EP07 ep07 ep07 0 headdriver 3 {load} enddynamic
|
||||
node -1 0 b dynamic PKP/111A 111a 111a 0 nobody 3 0 enddynamic
|
||||
endtrainset
|
||||
""";
|
||||
|
||||
var faults = TestScenery.Parser().Parse("scenery/x.scn", scn).Faults;
|
||||
|
||||
if (word is null)
|
||||
Assert.Empty(faults);
|
||||
else
|
||||
Assert.Equal((SceneryFaultKind.WordBeforeEnd, word, "b"), (faults.Single().Kind, faults.Single().Token, faults.Single().Lost));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_header_written_twice_is_told_as_such()
|
||||
{
|
||||
const string scn = """
|
||||
trainset baltyk\mod\exe45151 chojnice301 40 0.1
|
||||
//$o -Skład ozdobny
|
||||
trainset baltyk\mod\exe45151 chojnice301 40 0.1
|
||||
node -1 0 a dynamic PKP/EP07 ep07 ep07 0 headdriver 3 0 enddynamic
|
||||
endtrainset
|
||||
""";
|
||||
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
Assert.Equal((SceneryFaultKind.TrainsetHeaderRepeated, "x.scn", 1, "baltyk\\mod\\exe45151", null),
|
||||
Seen(Assert.Single(scenery.Faults)));
|
||||
Assert.Single(scenery.Trainsets);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_trainset_whose_header_is_swallowed_is_told_at_the_fault_that_began_it()
|
||||
{
|
||||
const string scn = """
|
||||
trainset pierwszy tor_1 10 0
|
||||
node -1 0 a dynamic PKP/EP07 ep07 ep07 0 headdriver 3 0 enddynamic
|
||||
endtrainset
|
||||
//trainset bez_naglowka tor_2 0 0
|
||||
node -1 0 b dynamic PKP/EP07 ep07 ep07 0 headdriver 3 0 enddynamic
|
||||
endtrainset
|
||||
trainset trzeci tor_3 0 0
|
||||
node -1 0 c1 dynamic PKP/EU07 eu07 eu07 0 headdriver 3 0 enddynamic
|
||||
node -1 0 c2 dynamic PKP/111A 111a 111a 0 nobody 3 0 enddynamic
|
||||
node -1 0 c3 dynamic PKP/111A 111a 111a 0 nobody 3 0 enddynamic
|
||||
endtrainset
|
||||
trainset czwarty tor_4 0 0
|
||||
node -1 0 d dynamic PKP/EP07 ep07 ep07 0 headdriver 3 0 enddynamic
|
||||
endtrainset
|
||||
""";
|
||||
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
// c2 with no trainset takes c3, and the endtrainset is left alone - all of
|
||||
// it from b, where the fault is.
|
||||
var fault = Assert.Single(scenery.Faults);
|
||||
Assert.Equal((SceneryFaultKind.VehicleOutsideTrainset, 5, "endtrainset, trainset trzeci, c1", "trzeci"),
|
||||
(fault.Kind, fault.Line, fault.Lost, fault.Unmade));
|
||||
Assert.Equal(new[] { "pierwszy", "trzeci", "czwarty" }, scenery.Trainsets.Select(t => t.Name));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Text_where_a_number_belongs_is_named()
|
||||
{
|
||||
const string scn = """
|
||||
trainset pociag tor_1 1O 0
|
||||
node -1 0 a dynamic PKP/EP07 ep07 ep07 0,5 headdriver 3 0 enddynamic
|
||||
endtrainset
|
||||
""";
|
||||
|
||||
var faults = TestScenery.Parser().Parse("scenery/x.scn", scn).Faults;
|
||||
|
||||
Assert.Equal(new[]
|
||||
{
|
||||
(SceneryFaultKind.NotANumber, "x.scn", 1, (string?)"pociag", (string?)"1O"),
|
||||
(SceneryFaultKind.NotANumber, "x.scn", 2, "a", "0,5")
|
||||
}, faults.Select(Seen));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_track_name_split_by_a_space_is_named_whole()
|
||||
{
|
||||
// "Pawonków#L" with its "ó" lost to a space.
|
||||
const string scn = """
|
||||
trainset none Pawonk w#L 200.0 0.0
|
||||
node -1 0 a dynamic PKP/EP07 ep07 ep07 0 headdriver 3 0 enddynamic
|
||||
endtrainset
|
||||
""";
|
||||
|
||||
var faults = TestScenery.Parser().Parse("scenery/x.scn", scn).Faults;
|
||||
|
||||
Assert.Equal((SceneryFaultKind.TrackNameSplit, "x.scn", 1, "none", "Pawonk w#L"), Seen(Assert.Single(faults)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Vehicles_left_without_their_trainset_header_are_told_so()
|
||||
{
|
||||
const string scn = """
|
||||
//trainset 2003r\64040 Staniszcze_Male_po#2 -50 0.1
|
||||
node -1 0 EN57-1542ra dynamic pkp\en57_v2 EN57-1542ra EN57RA 0 headdriver 255 0 enddynamic
|
||||
endtrainset
|
||||
node -1 0 auto dynamic road\ford transit transit droga-b 0 headdriver 25.0 0 enddynamic
|
||||
""";
|
||||
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
// On no track the simulator has, it drops the vehicle and goes on to the
|
||||
// next enddynamic - past the endtrainset, to the car's.
|
||||
var fault = Assert.Single(scenery.Faults);
|
||||
Assert.Equal((SceneryFaultKind.VehicleOutsideTrainset, 2, "endtrainset, auto"), (fault.Kind, fault.Line, fault.Lost));
|
||||
Assert.Equal("auto", Assert.Single(scenery.LooseVehicles).Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Structure_faults_are_found_where_the_simulator_reports_them()
|
||||
{
|
||||
const string scn = """
|
||||
endtrainset
|
||||
trainset pusty tor_1 10 0
|
||||
endtrainset trainset pociag tor_1 10 0
|
||||
node -1 0 a dynamic PKP/EP07 ep07 ep07 0 headdriver enddynamic
|
||||
node -1 0 b dynamic PKP/EP07 ep07 ep07 0 headdriver 3 0 enddynamic
|
||||
reversed
|
||||
endtrainset
|
||||
include dekoracje.scm // bez end
|
||||
include auta.scm end
|
||||
/* bez końca
|
||||
""";
|
||||
|
||||
var faults = TestScenery.Parser().Parse("scenery/x.scn", scn).Faults.Select(f => (f.Kind, f.Line)).ToList();
|
||||
|
||||
Assert.Equal(new[]
|
||||
{
|
||||
(SceneryFaultKind.StrayEndTrainset, 1),
|
||||
(SceneryFaultKind.VehicleIncomplete, 4),
|
||||
(SceneryFaultKind.ReversedMisplaced, 6),
|
||||
(SceneryFaultKind.IncludeNotClosed, 8),
|
||||
(SceneryFaultKind.UnclosedComment, 10)
|
||||
}, faults);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_trainset_with_no_vehicles_is_listed_to_be_filled_in_the_depot()
|
||||
{
|
||||
const string scn = """
|
||||
trainset td/rozklad tdo_n1734 0.0 0.1
|
||||
//$o Skład gotowy na żeberku.
|
||||
endtrainset
|
||||
include koniec.scm
|
||||
""";
|
||||
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn, _ => new IncludeLookup(true));
|
||||
|
||||
Assert.Empty(scenery.Faults);
|
||||
Assert.Equal("td/rozklad", Assert.Single(scenery.Trainsets).Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reversed_may_follow_the_assignments_ahead_of_the_vehicles()
|
||||
{
|
||||
const string scn = """
|
||||
Trainset pociag tor_1 10 0
|
||||
assignment pl "Pociąg" endassignment
|
||||
reversed
|
||||
Node -1 0 a dynamic PKP/EP07 ep07 ep07 0 HeadDriver 3 0 EndDynamic
|
||||
EndTrainset
|
||||
""";
|
||||
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn);
|
||||
|
||||
Assert.Empty(scenery.Faults);
|
||||
var trainset = Assert.Single(scenery.Trainsets);
|
||||
Assert.True(trainset.Reversed);
|
||||
Assert.Equal(StarterNG.Classes.eDriverType.Headdriver, Assert.Single(trainset.Vehicles).DriverType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_fault_in_an_included_file_is_placed_in_that_file()
|
||||
{
|
||||
string text = SceneryIncludes.Inline(
|
||||
"FirstInit\ninclude tomaszewo/auta.scm end\ntrainset zly tor 0\nendtrainset\n",
|
||||
name => (name, "// auta\ntrainset auto road1 0 50\nnode -1 0 a1 dynamic car car car x headdriver 3 0 enddynamic\nendtrainset"));
|
||||
|
||||
var faults = TestScenery.Parser().Parse("scenery/x.scn", text).Faults.Select(Seen);
|
||||
|
||||
Assert.Equal(new[]
|
||||
{
|
||||
(SceneryFaultKind.NotANumber, "tomaszewo/auta.scm", 3, (string?)"a1", (string?)"x"),
|
||||
(SceneryFaultKind.TrainsetIncomplete, "x.scn", 3, "zly", null)
|
||||
}, faults);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Missing_includes_are_reported_but_optional_ones_and_templates_are_not()
|
||||
{
|
||||
const string scn = """
|
||||
include tory.scm end
|
||||
include brak.scm end // komentarz
|
||||
include budynek.inc (p1) 0 0 end
|
||||
include $(p1)_dom.inc end
|
||||
include opcja.scm end //$optional 2,Opcja
|
||||
endoptional
|
||||
""";
|
||||
|
||||
var faults = TestScenery.Parser().Parse("scenery/x.scn", scn, name => new IncludeLookup(name is "tory.scm" or "budynek.inc")).Faults;
|
||||
|
||||
Assert.Equal((SceneryFaultKind.MissingInclude, "x.scn", 2, null, "brak.scm"), Seen(Assert.Single(faults)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void An_include_named_in_another_case_is_found_as_the_simulator_finds_it()
|
||||
{
|
||||
var files = new InMemoryFileSystem(caseSensitive: true)
|
||||
.WithLegacyFile("game/scenery/x.scn", "FirstInit\ninclude Tomaszewo/Auta.SCM end\n")
|
||||
.WithLegacyFile("game/scenery/tomaszewo/auta.scm",
|
||||
"trainset auto road1 0 50\nnode -1 0 a1 dynamic car car car 0 headdriver 3 0 enddynamic\nendtrainset\n");
|
||||
var repository = TestScenery.Repository(files);
|
||||
|
||||
var scenery = repository.Load("game/scenery/x.scn")!;
|
||||
|
||||
Assert.Empty(scenery.Faults);
|
||||
Assert.Equal("tomaszewo/auta.scm", Assert.Single(scenery.Trainsets).SourceFile);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void An_include_on_disk_only_in_other_letter_case_is_what_linux_misses()
|
||||
{
|
||||
// The simulator folds the name to lower case and opens it so.
|
||||
var files = new InMemoryFileSystem(caseSensitive: true)
|
||||
.WithLegacyFile("game/scenery/braniewo_szeroki.scn",
|
||||
"include braniewo/base.ctr end\ninclude Braniewo/Tory.SCM end\n")
|
||||
.WithFile("game/scenery/Braniewo/Base.ctr", "")
|
||||
.WithFile("game/scenery/braniewo/tory.scm", "");
|
||||
var repository = TestScenery.Repository(files);
|
||||
|
||||
var faults = repository.Load("game/scenery/braniewo_szeroki.scn")!.Faults;
|
||||
|
||||
Assert.Equal((SceneryFaultKind.IncludeLetterCase, "braniewo_szeroki.scn", 1, "Braniewo/Base.ctr", "braniewo/base.ctr"),
|
||||
Seen(Assert.Single(faults)));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void A_missing_terrain_file_is_no_fault_while_the_scenery_has_its_own_sbt(bool sbt)
|
||||
{
|
||||
var files = new InMemoryFileSystem(caseSensitive: true)
|
||||
.WithLegacyFile("game/scenery/Lk2_Sandbox.scn", "include nmt/nmt100_mazowieckie_ter.scm end\nFirstInit\n");
|
||||
if (sbt)
|
||||
files.WithFile("game/scenery/lk2_sandbox.sbt", "EU07");
|
||||
var repository = TestScenery.Repository(files);
|
||||
|
||||
var scenery = repository.Load("game/scenery/Lk2_Sandbox.scn")!;
|
||||
|
||||
Assert.Equal(sbt, scenery.HasCompanionTimetable);
|
||||
Assert.Equal(sbt ? 0 : 1, scenery.Faults.Count);
|
||||
}
|
||||
}
|
||||
578
StarterNG.Tests/SceneryTrainsetSaveTests.cs
Normal file
578
StarterNG.Tests/SceneryTrainsetSaveTests.cs
Normal file
@@ -0,0 +1,578 @@
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using StarterNG.Application;
|
||||
using StarterNG.Application.Abstractions;
|
||||
using StarterNG.Classes;
|
||||
using StarterNG.Domain.Sceneries;
|
||||
using StarterNG.Infrastructure.Adapters;
|
||||
using StarterNG.Tests.Fakes;
|
||||
|
||||
namespace StarterNG.Tests;
|
||||
|
||||
public class SceneryTrainsetSaveTests
|
||||
{
|
||||
private const string Path = "scenery/test.scn";
|
||||
|
||||
private const string Scn =
|
||||
"//$n Bałtyk\r\n" +
|
||||
"FirstInit\r\n" +
|
||||
"trainset pociag tor_1 200.0 0.0\r\n" +
|
||||
"//$o -Opis składu\r\n" +
|
||||
"node -1 0 ep07-001 dynamic PKP\\EP07 ep07-001 ep07 0 headdriver 3 0 enddynamic\r\n" +
|
||||
"node -1 0 wagon-1 dynamic PKP\\111A b10-1 111a 0 nobody 3 0 enddynamic\r\n" +
|
||||
"endtrainset\r\n" +
|
||||
"// koniec pliku z polską literą: żółw\r\n";
|
||||
|
||||
private static Encoding Cp1250 => Encoding.GetEncoding(1250);
|
||||
|
||||
private static (InMemoryFileSystem Files, Scenery Scenery, SaveSceneryTrainset Save) Setup(string scn) =>
|
||||
Setup(Cp1250.GetBytes(scn));
|
||||
|
||||
private static (InMemoryFileSystem Files, Scenery Scenery, SaveSceneryTrainset Save) Setup(byte[] bytes)
|
||||
{
|
||||
var files = new InMemoryFileSystem();
|
||||
files.WriteAllBytes(Path, bytes);
|
||||
var scenery = TestScenery.Parser().Parse(Path, LegacyText.Decode(bytes));
|
||||
return (files, scenery, new SaveSceneryTrainset(files, TestLog.Over(files)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reversed_after_the_header_is_read_and_written_back()
|
||||
{
|
||||
var trainset = new Trainset(
|
||||
"trainset a tor 0 0 reversed\n" +
|
||||
"node -1 0 v1 dynamic d s m 0 nobody 3 0 enddynamic\n" +
|
||||
"endtrainset");
|
||||
|
||||
Assert.True(trainset.Reversed);
|
||||
Assert.Single(trainset.Vehicles);
|
||||
Assert.StartsWith("trainset a tor 0 0 reversed\n", trainset.GetTrainsetEntry());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_trainset_without_reversed_is_not_turned()
|
||||
{
|
||||
var trainset = new Trainset("trainset a tor 0 0\nendtrainset");
|
||||
|
||||
Assert.False(trainset.Reversed);
|
||||
Assert.DoesNotContain("reversed", trainset.GetTrainsetEntry());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void An_untouched_trainset_is_not_modified_however_its_numbers_are_spelled()
|
||||
{
|
||||
var (_, scenery, _) = Setup(Scn);
|
||||
|
||||
Assert.False(SceneryTrainsetText.IsModified(scenery.Trainsets[0]));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_head_stays_as_written_and_the_vehicles_are_rewritten()
|
||||
{
|
||||
var (_, scenery, _) = Setup(Scn.Replace("200.0 0.0\r\n", "200.0 0.0\r\nreversed\r\n"));
|
||||
var trainset = scenery.Trainsets[0];
|
||||
trainset.Vehicles.RemoveAt(1);
|
||||
|
||||
string entry = SceneryTrainsetText.Compose(trainset, "\r\n");
|
||||
|
||||
Assert.StartsWith("trainset pociag tor_1 200.0 0.0\r\nreversed\r\n//$o -Opis składu\r\nnode", entry);
|
||||
Assert.Contains("ep07-001", entry);
|
||||
Assert.DoesNotContain("wagon-1", entry);
|
||||
Assert.EndsWith("enddynamic\r\nendtrainset", entry);
|
||||
Assert.DoesNotContain("\r\r", entry);
|
||||
Assert.DoesNotMatch("[^\r]\n", entry);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Only_the_speed_of_the_header_follows_the_ready_switch()
|
||||
{
|
||||
var (_, scenery, _) = Setup(Scn);
|
||||
var trainset = scenery.Trainsets[0];
|
||||
trainset.ReadyToGo = true;
|
||||
|
||||
string entry = SceneryTrainsetText.Compose(trainset, "\n");
|
||||
|
||||
Assert.StartsWith("trainset pociag tor_1 200.0 0.1\n", entry);
|
||||
Assert.True(SceneryTrainsetText.IsModified(trainset));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Saving_rewrites_the_trainset_alone_and_keeps_the_original_once()
|
||||
{
|
||||
var (files, scenery, save) = Setup(Scn);
|
||||
var trainset = scenery.Trainsets[0];
|
||||
trainset.Vehicles.RemoveAt(1);
|
||||
|
||||
var (outcome, _) = save.Execute(scenery, trainset);
|
||||
|
||||
Assert.Equal(SceneryTrainsetSaveOutcome.Saved, outcome);
|
||||
string written = Cp1250.GetString(files.ReadAllBytes(Path));
|
||||
Assert.StartsWith("//$n Bałtyk\r\nFirstInit\r\ntrainset pociag tor_1 200.0 0.0\r\n//$o -Opis składu\r\n", written);
|
||||
Assert.DoesNotContain("wagon-1", written);
|
||||
Assert.EndsWith("endtrainset\r\n// koniec pliku z polską literą: żółw\r\n", written);
|
||||
Assert.Equal(Scn, Cp1250.GetString(files.ReadAllBytes(Path + SaveSceneryTrainset.BackupSuffix)));
|
||||
Assert.False(SceneryTrainsetText.IsModified(trainset));
|
||||
|
||||
// A second save finds what the first one wrote; the backup stays the original.
|
||||
trainset.Vehicles.Clear();
|
||||
Assert.Equal(SceneryTrainsetSaveOutcome.Saved, save.Execute(scenery, trainset).Outcome);
|
||||
Assert.DoesNotContain("ep07-001", Cp1250.GetString(files.ReadAllBytes(Path)));
|
||||
Assert.Equal(Scn, Cp1250.GetString(files.ReadAllBytes(Path + SaveSceneryTrainset.BackupSuffix)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Of_two_trainsets_written_alike_the_right_one_is_saved()
|
||||
{
|
||||
const string block =
|
||||
"trainset none tor_1 0 0\r\n" +
|
||||
"node -1 0 v1 dynamic d s m 0 nobody 3 0 enddynamic\r\n" +
|
||||
"endtrainset\r\n";
|
||||
var (files, scenery, save) = Setup(block + "// x\r\n" + block);
|
||||
var second = scenery.Trainsets[1];
|
||||
second.Vehicles.Clear();
|
||||
|
||||
Assert.Equal(SceneryTrainsetSaveOutcome.Saved, save.Execute(scenery, second).Outcome);
|
||||
|
||||
string written = Cp1250.GetString(files.ReadAllBytes(Path));
|
||||
Assert.StartsWith(block + "// x\r\ntrainset none tor_1 0 0\r\nendtrainset", written);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_file_changed_meanwhile_is_left_alone()
|
||||
{
|
||||
var (files, scenery, save) = Setup(Scn);
|
||||
var trainset = scenery.Trainsets[0];
|
||||
trainset.Vehicles.Clear();
|
||||
files.WithLegacyFile(Path, Scn.Replace("wagon-1", "wagon-2"));
|
||||
|
||||
Assert.Equal(SceneryTrainsetSaveOutcome.NotFound, save.Execute(scenery, trainset).Outcome);
|
||||
Assert.Contains("wagon-2", Cp1250.GetString(files.ReadAllBytes(Path)));
|
||||
Assert.False(files.FileExists(Path + SaveSceneryTrainset.BackupSuffix));
|
||||
}
|
||||
|
||||
private const string Cars =
|
||||
"trainset none road1 -20 50\r\n" +
|
||||
"node -1 0 auto_1 dynamic road\\man man nl293 0 headdriver 0 0 enddynamic\r\n" +
|
||||
"endtrainset\r\n";
|
||||
|
||||
private const string Traffic =
|
||||
"node -1 0 nl293_a dynamic road\\man man nl293 \t\tryp_rd110 \t0 headdriver 50 0 enddynamic\r\n";
|
||||
|
||||
private const string WithIncludes =
|
||||
"include tory.scm end\r\n" +
|
||||
"FirstInit\r\n" +
|
||||
"trainset pociag tor_1 0 0\r\n" +
|
||||
"node -1 0 ep07-001 dynamic PKP\\EP07 ep07-001 ep07 0 headdriver 3 0 enddynamic\r\n" +
|
||||
"endtrainset\r\n" +
|
||||
"include tomaszewo/auta.scm end //ruch uliczny\r\n" +
|
||||
"include tomaszewo/eventy.ctr end\r\n" +
|
||||
"/* include tomaszewo/auta.scm end */\r\n" +
|
||||
"include tomaszewo/auta.scm parametr end\r\n";
|
||||
|
||||
private static (string File, string Text)? Files(string name) => name switch
|
||||
{
|
||||
"tory.scm" => (name, Cars),
|
||||
"tomaszewo/auta.scm" => (name, Cars + Traffic),
|
||||
"tomaszewo/eventy.ctr" => (name, "event e1 multiple 0 none endevent\r\n"),
|
||||
_ => null
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void Includes_after_first_init_that_hold_trainsets_are_put_in_place()
|
||||
{
|
||||
string text = SceneryIncludes.Inline(WithIncludes, Files);
|
||||
|
||||
// Before FirstInit is the scenery itself; without trainsets there is nothing to list.
|
||||
Assert.StartsWith("include tory.scm end\r\n", text);
|
||||
Assert.Contains("include tomaszewo/eventy.ctr end", text);
|
||||
Assert.Contains(SceneryIncludes.BeginMarker + "tomaszewo/auta.scm\r\ntrainset none road1", text);
|
||||
// Commented out, or with parameters to put in: left to the simulator.
|
||||
Assert.Contains("/* include tomaszewo/auta.scm end */", text);
|
||||
Assert.Contains("include tomaszewo/auta.scm parametr end", text);
|
||||
Assert.Single(SceneryIncludes.Ranges(text));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_trainset_from_an_include_is_listed_with_its_file_and_exported_in_place()
|
||||
{
|
||||
var scenery = TestScenery.Parser().Parse(Path, SceneryIncludes.Inline(WithIncludes, Files));
|
||||
|
||||
Assert.Equal(2, scenery.Trainsets.Count);
|
||||
Assert.Null(scenery.Trainsets[0].SourceFile);
|
||||
Assert.Equal("tomaszewo/auta.scm", scenery.Trainsets[1].SourceFile);
|
||||
|
||||
// Loose road traffic in the file stays put, as written; only its name is known.
|
||||
Assert.Empty(scenery.LooseVehicles);
|
||||
Assert.Contains("nl293_a", scenery.LooseVehicleNames);
|
||||
|
||||
string exported = scenery.BuildExportContent(skipDecorTrainsets: false, new StubRandom());
|
||||
Assert.Contains("auto_1", exported);
|
||||
Assert.Contains(Traffic, exported);
|
||||
Assert.True(exported.IndexOf(Traffic, StringComparison.Ordinal) > exported.IndexOf("FirstInit", StringComparison.Ordinal));
|
||||
Assert.DoesNotContain("include tomaszewo/auta.scm end //", exported);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_trainset_from_an_include_is_saved_into_that_file()
|
||||
{
|
||||
var files = new InMemoryFileSystem()
|
||||
.WithLegacyFile(Path, WithIncludes)
|
||||
.WithLegacyFile("scenery/tomaszewo/auta.scm", Cars + "// koniec\r\n");
|
||||
var scenery = TestScenery.Parser().Parse(Path, SceneryIncludes.Inline(WithIncludes, Files));
|
||||
var log = TestLog.Over(files);
|
||||
var car = scenery.Trainsets[1];
|
||||
car.Vehicles.Clear();
|
||||
|
||||
Assert.Equal(SceneryTrainsetSaveOutcome.Saved, new SaveSceneryTrainset(files, log).Execute(scenery, car).Outcome);
|
||||
|
||||
Assert.Equal("trainset none road1 -20 50\r\nendtrainset\r\n// koniec\r\n",
|
||||
Cp1250.GetString(files.ReadAllBytes("scenery/tomaszewo/auta.scm")));
|
||||
Assert.Equal(WithIncludes, Cp1250.GetString(files.ReadAllBytes(Path)));
|
||||
Assert.True(files.FileExists("scenery/tomaszewo/auta.scm" + SaveSceneryTrainset.BackupSuffix));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_utf8_file_stays_utf8_with_its_mark()
|
||||
{
|
||||
var (files, scenery, save) = Setup(new byte[] { 0xEF, 0xBB, 0xBF }.Concat(Encoding.UTF8.GetBytes(Scn)).ToArray());
|
||||
scenery.Trainsets[0].Vehicles.RemoveAt(1);
|
||||
|
||||
save.Execute(scenery, scenery.Trainsets[0]);
|
||||
|
||||
byte[] bytes = files.ReadAllBytes(Path);
|
||||
Assert.Equal(new byte[] { 0xEF, 0xBB, 0xBF }, bytes[..3]);
|
||||
Assert.Contains("żółw", Encoding.UTF8.GetString(bytes[3..]));
|
||||
}
|
||||
|
||||
private const string V1 = "node -1 0 v1 dynamic d s m 0 nobody 3 0 enddynamic";
|
||||
private const string V2 = "node -1 0 v2 dynamic d s m 0 nobody 3 0 enddynamic";
|
||||
private const string V3 = "node -1 0 v3 dynamic d s m 0 nobody 3 0 enddynamic";
|
||||
|
||||
private static string SavedText(string scn, Action<Trainset>? change = null)
|
||||
{
|
||||
var (files, scenery, save) = Setup(scn);
|
||||
change?.Invoke(scenery.Trainsets[0]);
|
||||
|
||||
Assert.Equal(SceneryTrainsetSaveOutcome.Saved, save.Execute(scenery, scenery.Trainsets[0]).Outcome);
|
||||
|
||||
return Cp1250.GetString(files.ReadAllBytes(Path));
|
||||
}
|
||||
|
||||
private static IEnumerable<string> VehicleNames(string scn) =>
|
||||
TestScenery.Parser().Parse(Path, scn).Trainsets.Single().Vehicles.Select(v => v.Name);
|
||||
|
||||
private static void Edit(Trainset trainset) => trainset.Vehicles[0].Coupling.Flags = 5;
|
||||
|
||||
[Fact]
|
||||
public void A_vehicle_commented_out_stays_in_its_comment()
|
||||
{
|
||||
string scn = $"FirstInit\ntrainset a t 0 0\n/*\n{V1}\n*/\n{V2}\nendtrainset\n" +
|
||||
"node -1 0 poza dynamic d s m none 0 nobody 0 0 enddynamic\n";
|
||||
|
||||
Assert.Equal(scn, SavedText(scn));
|
||||
|
||||
string edited = SavedText(scn, Edit);
|
||||
Assert.Contains($"/*\n{V1}\n*/\n", edited);
|
||||
var scenery = TestScenery.Parser().Parse(Path, edited);
|
||||
Assert.Equal(new[] { "v2" }, scenery.Trainsets.Single().Vehicles.Select(v => v.Name));
|
||||
Assert.Equal(new[] { "poza" }, scenery.LooseVehicleNames);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("trainset a t 0 0\nNode -1 0 v1 dynamic d s m 0 nobody 3 0 enddynamic\nendtrainset\n")]
|
||||
[InlineData("trainset a t 0 0\nNODE -1 0 v1 dynamic d s m 0 nobody 3 0 enddynamic\nendtrainset\n")]
|
||||
[InlineData("trainset a t 0 0 node -1 0 v1 dynamic d s m 0 nobody 3 0 enddynamic\nendtrainset\n")]
|
||||
[InlineData("trainset a t 0 0 /*x*/node -1 0 v1 dynamic d s m 0 nobody 3 0 enddynamic\nendtrainset\n")]
|
||||
public void A_vehicle_is_found_however_it_is_written_and_not_written_twice(string scn)
|
||||
{
|
||||
Assert.Equal(scn, SavedText(scn));
|
||||
|
||||
string edited = SavedText(scn, Edit);
|
||||
var vehicle = Assert.Single(TestScenery.Parser().Parse(Path, edited).Trainsets.Single().Vehicles);
|
||||
Assert.Equal(5, vehicle.Coupling.Flags);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_vehicle_named_as_a_keyword_is_no_keyword()
|
||||
{
|
||||
string scn = "trainset node node 0 0\nnode -1 0 node dynamic d s m 0 nobody 3 0 enddynamic\nendtrainset\n";
|
||||
|
||||
Assert.Equal(scn, SavedText(scn));
|
||||
Assert.Contains("trainset node node 0 0.1\n", SavedText(scn, t => t.ReadyToGo = true));
|
||||
}
|
||||
|
||||
private const string Commented =
|
||||
"trainset a t 0 0\n" +
|
||||
V1 + "\n" +
|
||||
"// o v2\n" +
|
||||
V2 + " // przy v2\n" +
|
||||
"/* o v3 */\n" +
|
||||
V3 + "\n" +
|
||||
"// koniec\n" +
|
||||
"EndTrainset\n";
|
||||
|
||||
[Fact]
|
||||
public void A_save_with_no_change_leaves_the_vehicles_and_comments_as_they_were()
|
||||
{
|
||||
Assert.Equal(Commented, SavedText(Commented));
|
||||
|
||||
// Nor is anything but the speed touched when only the speed changes.
|
||||
Assert.Equal(Commented.Replace("t 0 0\n", "t 0 0.1\n"), SavedText(Commented, t => t.ReadyToGo = true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Comments_stay_where_they_are_when_a_vehicle_is_edited()
|
||||
{
|
||||
string edited = SavedText(Commented, t => t.Vehicles[1].Coupling.Flags = 5);
|
||||
|
||||
Assert.Equal(Commented.Replace(V2, V2.Replace("nobody 3", "nobody 5")), edited);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Comments_stay_when_a_vehicle_is_removed()
|
||||
{
|
||||
Assert.Equal(
|
||||
"trainset a t 0 0\n" + V1 + "\n// o v2\n// przy v2\n/* o v3 */\n" + V3 + "\n// koniec\nEndTrainset\n",
|
||||
SavedText(Commented, t => t.Vehicles.RemoveAt(1)));
|
||||
|
||||
Assert.Equal(
|
||||
"trainset a t 0 0\n// o v2\n// przy v2\n/* o v3 */\n// koniec\nEndTrainset\n",
|
||||
SavedText(Commented, t => t.Vehicles.Clear()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Comments_stay_when_a_vehicle_is_added()
|
||||
{
|
||||
var added = new Dynamic { Name = "nowy", DataFolder = "d", SkinFile = "s", MmdFile = "m" };
|
||||
|
||||
Assert.Equal(
|
||||
Commented.Replace(V3, added.ToTrainsetNode() + V3),
|
||||
SavedText(Commented, t => t.Vehicles.Insert(2, added)));
|
||||
|
||||
Assert.Equal(
|
||||
Commented.Replace("EndTrainset", added.ToTrainsetNode() + "EndTrainset"),
|
||||
SavedText(Commented, t => t.Vehicles.Add(added)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Comments_stay_when_vehicles_change_places_and_a_moved_one_stays_as_written()
|
||||
{
|
||||
string moved = "node 500 0 v3 dynamic d s m /*w srodku*/ 0 nobody 3 0 enddynamic";
|
||||
string scn = Commented.Replace(V3, moved);
|
||||
|
||||
string saved = SavedText(scn, t => t.Vehicles.Reverse());
|
||||
|
||||
Assert.Equal(new[] { "v3", "v2", "v1" }, VehicleNames(saved));
|
||||
foreach (string comment in new[] { "// o v2\n", " // przy v2\n", "/* o v3 */\n", "// koniec\n" })
|
||||
Assert.Contains(comment, saved);
|
||||
Assert.Contains(moved, saved);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("trainset a /*tor*/ t 100 0\nendtrainset\n", "trainset a /*tor*/ t 100 0.1\nendtrainset\n")]
|
||||
[InlineData("trainset a t /*x*/ 100/*y*/ 0.0 reversed\nendtrainset\n", "trainset a t /*x*/ 100/*y*/ 0.1 reversed\nendtrainset\n")]
|
||||
[InlineData("Trainset a // tor\n t\n 100\n 0.0\nendtrainset\n", "Trainset a // tor\n t\n 100\n 0.1\nendtrainset\n")]
|
||||
public void The_speed_is_found_past_a_comment_in_the_header(string scn, string expected)
|
||||
{
|
||||
Assert.Equal(expected, SavedText(scn, t => t.ReadyToGo = true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void An_optional_include_after_first_init_stays_an_option()
|
||||
{
|
||||
const string scn = "FirstInit\ninclude zima.inc end //$optional 1, Zima\nx\nendoptional\n";
|
||||
string content = $"trainset a t 0 0\n{V1}\nendtrainset\n";
|
||||
|
||||
string text = SceneryIncludes.Inline(scn, name => (name, content));
|
||||
|
||||
Assert.Equal(scn, text);
|
||||
var scenery = TestScenery.Parser().Parse(Path, text);
|
||||
Assert.Equal("Zima", Assert.Single(scenery.Includes).Desc);
|
||||
Assert.Empty(scenery.Trainsets);
|
||||
}
|
||||
|
||||
/// <summary>A disk that gives out on whatever is being written; what it does to the file is <see cref="PhysicalFileSystemTests"/>' business.</summary>
|
||||
public class FailingDisk : DispatchProxy
|
||||
{
|
||||
public InMemoryFileSystem Files = null!;
|
||||
|
||||
protected override object? Invoke(MethodInfo? method, object?[]? args) =>
|
||||
method!.Name == nameof(IFileSystem.WriteAllBytesAtomic)
|
||||
? throw new IOException("the disk gave out")
|
||||
: method.Invoke(Files, args);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void A_write_that_fails_leaves_the_file_and_its_backup_whole(bool backedUp)
|
||||
{
|
||||
var (files, scenery, _) = Setup(Scn);
|
||||
string backup = Path + SaveSceneryTrainset.BackupSuffix;
|
||||
if (backedUp)
|
||||
files.WithLegacyFile(backup, Scn);
|
||||
var disk = DispatchProxy.Create<IFileSystem, FailingDisk>();
|
||||
((FailingDisk)(object)disk).Files = files;
|
||||
var log = TestLog.Over();
|
||||
var trainset = scenery.Trainsets[0];
|
||||
string before = trainset.RawEntry;
|
||||
trainset.Vehicles.RemoveAt(1);
|
||||
|
||||
var (outcome, detail) = new SaveSceneryTrainset(disk, log).Execute(scenery, trainset);
|
||||
|
||||
Assert.Equal((SceneryTrainsetSaveOutcome.Failed, "the disk gave out"), (outcome, detail));
|
||||
Assert.Equal(Scn, Cp1250.GetString(files.ReadAllBytes(Path)));
|
||||
Assert.Equal(backedUp, files.FileExists(backup));
|
||||
Assert.Equal(new[] { Path }.Concat(backedUp ? new[] { backup } : Array.Empty<string>()),
|
||||
files.GetFiles("scenery"));
|
||||
Assert.Equal(before, trainset.RawEntry);
|
||||
}
|
||||
|
||||
private static (string Scn, SaveSceneryTrainset Save, Scenery Scenery, string Folder) OnDisk(string text)
|
||||
{
|
||||
string folder = Directory.CreateTempSubdirectory("starter-save-").FullName;
|
||||
string scn = System.IO.Path.Combine(folder, "test.scn");
|
||||
var files = new PhysicalFileSystem();
|
||||
files.WriteAllText(scn, text, LegacyText.CodePage1250);
|
||||
var log = TestLog.Over();
|
||||
return (scn, new SaveSceneryTrainset(files, log), TestScenery.Parser().Parse(scn, text), folder);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void On_disk_the_new_text_is_written_beside_the_file_and_moved_over_it()
|
||||
{
|
||||
var (scn, save, scenery, folder) = OnDisk(Scn);
|
||||
try
|
||||
{
|
||||
var trainset = scenery.Trainsets[0];
|
||||
trainset.Vehicles.RemoveAt(1);
|
||||
|
||||
// Nothing can be written beside it: the file is not touched either.
|
||||
string temp = scn + PhysicalFileSystem.TempSuffix;
|
||||
Directory.CreateDirectory(temp);
|
||||
Assert.Equal(SceneryTrainsetSaveOutcome.Failed, save.Execute(scenery, trainset).Outcome);
|
||||
Assert.Equal(Scn, Cp1250.GetString(File.ReadAllBytes(scn)));
|
||||
Assert.True(File.Exists(scn + SaveSceneryTrainset.BackupSuffix));
|
||||
|
||||
Directory.Delete(temp);
|
||||
Assert.Equal((SceneryTrainsetSaveOutcome.Saved, (string?)null), save.Execute(scenery, trainset));
|
||||
|
||||
Assert.DoesNotContain("wagon-1", Cp1250.GetString(File.ReadAllBytes(scn)));
|
||||
Assert.Equal(Scn, Cp1250.GetString(File.ReadAllBytes(scn + SaveSceneryTrainset.BackupSuffix)));
|
||||
Assert.Equal(
|
||||
new[] { "test.scn", "test.scn" + SaveSceneryTrainset.BackupSuffix },
|
||||
Directory.GetFiles(folder, "test.scn*").Select(file => System.IO.Path.GetFileName(file)).Order());
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(folder, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void On_disk_a_file_that_cannot_be_replaced_stays_and_nothing_is_left_beside_it()
|
||||
{
|
||||
// Only Windows keeps a file in use from being moved over.
|
||||
if (!OperatingSystem.IsWindows())
|
||||
return;
|
||||
|
||||
var (scn, save, scenery, folder) = OnDisk(Scn);
|
||||
try
|
||||
{
|
||||
scenery.Trainsets[0].Vehicles.RemoveAt(1);
|
||||
|
||||
using (new FileStream(scn, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
Assert.Equal(SceneryTrainsetSaveOutcome.Failed, save.Execute(scenery, scenery.Trainsets[0]).Outcome);
|
||||
|
||||
Assert.Equal(Scn, Cp1250.GetString(File.ReadAllBytes(scn)));
|
||||
Assert.False(File.Exists(scn + PhysicalFileSystem.TempSuffix));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(folder, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void On_disk_a_link_stays_a_link_and_its_file_gets_the_text()
|
||||
{
|
||||
var (scn, save, scenery, folder) = OnDisk(Scn);
|
||||
try
|
||||
{
|
||||
string target = scn + ".wlasciwy";
|
||||
File.Move(scn, target);
|
||||
try
|
||||
{
|
||||
File.CreateSymbolicLink(scn, target);
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
|
||||
{
|
||||
// Windows lets only some users make links.
|
||||
return;
|
||||
}
|
||||
scenery.Trainsets[0].Vehicles.RemoveAt(1);
|
||||
|
||||
Assert.Equal(SceneryTrainsetSaveOutcome.Saved, save.Execute(scenery, scenery.Trainsets[0]).Outcome);
|
||||
|
||||
Assert.NotNull(new FileInfo(scn).LinkTarget);
|
||||
Assert.DoesNotContain("wagon-1", Cp1250.GetString(File.ReadAllBytes(target)));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(folder, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] Bytes(params object[] parts) =>
|
||||
parts.SelectMany(part => part as byte[] ?? Encoding.ASCII.GetBytes((string)part)).ToArray();
|
||||
|
||||
[Fact]
|
||||
public void Bytes_behind_a_mark_that_are_not_utf8_stay_as_they_were()
|
||||
{
|
||||
byte[] mark = { 0xEF, 0xBB, 0xBF };
|
||||
byte[] polish = { 0xB3, 0xF3, 0xBF }; // "łóż" in code page 1250
|
||||
string trainset = $"trainset a t 0 0\n{V1}\nendtrainset\n";
|
||||
var (files, scenery, save) = Setup(Bytes(mark, "// ", polish, "\n", trainset, "// ", polish, "\n"));
|
||||
scenery.Trainsets[0].Vehicles.Clear();
|
||||
|
||||
Assert.Equal(SceneryTrainsetSaveOutcome.Saved, save.Execute(scenery, scenery.Trainsets[0]).Outcome);
|
||||
|
||||
Assert.Equal(Bytes(mark, "// ", polish, "\ntrainset a t 0 0\nendtrainset\n// ", polish, "\n"), files.ReadAllBytes(Path));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public void Every_byte_outside_the_vehicles_stays_as_it_was(bool marked)
|
||||
{
|
||||
byte[] mark = marked ? new byte[] { 0xEF, 0xBB, 0xBF } : Array.Empty<byte>();
|
||||
byte[] all = Enumerable.Range(0, 256).Select(b => (byte)b).ToArray();
|
||||
string trainset = $"trainset a t 0 0\n{V1}\nendtrainset\n";
|
||||
var (files, scenery, save) = Setup(Bytes(mark, trainset, all));
|
||||
scenery.Trainsets[0].Vehicles.Clear();
|
||||
|
||||
Assert.Equal(SceneryTrainsetSaveOutcome.Saved, save.Execute(scenery, scenery.Trainsets[0]).Outcome);
|
||||
|
||||
Assert.Equal(Bytes(mark, "trainset a t 0 0\nendtrainset\n", all), files.ReadAllBytes(Path));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
// Its own line ends, in a file with the other kind elsewhere.
|
||||
[InlineData("// x\r\ntrainset a t 0 0\n" + V1 + "\nendtrainset\n// y\r\n")]
|
||||
// Both kinds in the trainset.
|
||||
[InlineData("trainset a t 0 0\r\n// x\n" + V1 + "\r\nendtrainset\r\n")]
|
||||
public void Mixed_line_ends_stay_as_they_were(string scn)
|
||||
{
|
||||
Assert.Equal(scn, SavedText(scn));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void What_the_starter_writes_takes_the_line_ends_of_the_trainset()
|
||||
{
|
||||
string scn = "// x\r\ntrainset a t 0 0\n" + V1 + "\nendtrainset\n// y\r\n";
|
||||
|
||||
Assert.Equal(
|
||||
"// x\r\ntrainset a t 0 0\n" + V2 + "\nendtrainset\n// y\r\n",
|
||||
SavedText(scn, t => t.Vehicles[0].Name = "v2"));
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,10 @@
|
||||
using StarterNG.Domain.Sceneries;
|
||||
using StarterNG.Infrastructure.Sceneries;
|
||||
using StarterNG.Tests.Fakes;
|
||||
|
||||
namespace StarterNG.Tests;
|
||||
|
||||
public class SceneryWeatherTests
|
||||
{
|
||||
private static SceneryParser Parser(DateTime now) => new(new FixedClock(now), new StubRandom());
|
||||
|
||||
[Fact]
|
||||
public void Authored_weather_is_read_from_the_config_block()
|
||||
{
|
||||
@@ -21,7 +18,7 @@ public class SceneryWeatherTests
|
||||
time 8:30 0 0 endtime
|
||||
""";
|
||||
|
||||
var weather = Parser(new DateTime(2026, 1, 1)).Parse("scenery/x.scn", scn).Weather;
|
||||
var weather = TestScenery.Parser(new DateTime(2026, 1, 1)).Parse("scenery/x.scn", scn).Weather;
|
||||
|
||||
Assert.True(weather.IsAuthored);
|
||||
Assert.Equal(172, weather.Day);
|
||||
@@ -35,7 +32,7 @@ public class SceneryWeatherTests
|
||||
[Fact]
|
||||
public void A_scenery_without_a_time_starts_at_the_players_clock()
|
||||
{
|
||||
var weather = Parser(new DateTime(2026, 5, 4, 17, 42, 0)).Parse("scenery/x.scn", "//$n Test").Weather;
|
||||
var weather = TestScenery.Parser(new DateTime(2026, 5, 4, 17, 42, 0)).Parse("scenery/x.scn", "//$n Test").Weather;
|
||||
|
||||
Assert.Equal("17:42", weather.ScenarioTimeOverride);
|
||||
Assert.False(weather.IsAuthored);
|
||||
@@ -44,7 +41,7 @@ public class SceneryWeatherTests
|
||||
[Fact]
|
||||
public void Freshly_parsed_weather_counts_as_unchanged()
|
||||
{
|
||||
var weather = Parser(new DateTime(2026, 1, 1)).Parse("scenery/x.scn", "movelight 100\n").Weather;
|
||||
var weather = TestScenery.Parser(new DateTime(2026, 1, 1)).Parse("scenery/x.scn", "movelight 100\n").Weather;
|
||||
|
||||
Assert.False(weather.Changed);
|
||||
}
|
||||
@@ -52,7 +49,7 @@ public class SceneryWeatherTests
|
||||
[Fact]
|
||||
public void Editing_then_restoring_returns_to_the_authored_values()
|
||||
{
|
||||
var weather = Parser(new DateTime(2026, 1, 1)).Parse("scenery/x.scn", "movelight 100\n").Weather;
|
||||
var weather = TestScenery.Parser(new DateTime(2026, 1, 1)).Parse("scenery/x.scn", "movelight 100\n").Weather;
|
||||
|
||||
weather.Day = 5;
|
||||
weather.Temperature = -12;
|
||||
@@ -84,4 +81,168 @@ public class SceneryWeatherTests
|
||||
Assert.Equal(-1.5, weather.EffectiveOvercast(new StubRandom()));
|
||||
Assert.Equal(0.42, new SceneryWeather { Overcast = 0.42 }.EffectiveOvercast(new StubRandom()));
|
||||
}
|
||||
|
||||
private static string Rewritten(string scn) =>
|
||||
SceneryWeatherWriter.Rewrite(scn, new SceneryWeather(), new StubRandom());
|
||||
|
||||
private static string Code(string text) => SceneryCode.Mask(text, out _);
|
||||
|
||||
/// <summary>The block the writer puts at the top of the text, and the rest.</summary>
|
||||
private static (string Written, string Others) Split(string rewritten)
|
||||
{
|
||||
const string end = "endtime\r\n";
|
||||
int at = rewritten.IndexOf(end, StringComparison.Ordinal) + end.Length;
|
||||
return (rewritten[..at], rewritten[at..]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_config_commented_out_stays_off_and_the_one_after_it_is_kept()
|
||||
{
|
||||
// As linia61_osobowy2.scn has them.
|
||||
const string commented = "//config pause yes endconfig //yes: zatrzymanie symulacji zaraz po wczytaniu, jeśli start jako jadący";
|
||||
string scn = $"""
|
||||
sky skydome_clouds.t3d endsky
|
||||
atmo 0 0 0 1000 2000 0.341 0.408 0.471 -1.2 endatmo
|
||||
time 6:43 06:07 21:02 endtime //w Lublińcu 7:30
|
||||
|
||||
{commented}
|
||||
config hiddenevents 1 endconfig
|
||||
|
||||
include linia61/czestochowa.scm end
|
||||
|
||||
""";
|
||||
|
||||
var (written, rest) = Split(Rewritten(scn));
|
||||
|
||||
Assert.Contains("hiddenevents 1\r\n", written);
|
||||
Assert.DoesNotContain("pause", written);
|
||||
Assert.Contains(commented, rest);
|
||||
Assert.Contains("//w Lublińcu 7:30", rest);
|
||||
Assert.Contains("sky skydome_clouds.t3d endsky", rest);
|
||||
Assert.Contains("include linia61/czestochowa.scm end", rest);
|
||||
Assert.DoesNotContain("hiddenevents", rest);
|
||||
foreach (string word in new[] { "atmo", "time", "pause" })
|
||||
Assert.DoesNotContain(word, Code(rest));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_keyword_in_a_comment_does_not_cut_up_to_the_next_block()
|
||||
{
|
||||
const string scn = """
|
||||
// atmo: tu byla mgla
|
||||
include a.scm end
|
||||
{{0}}
|
||||
/* config */
|
||||
include b.scm end
|
||||
{{1}}
|
||||
// time 8 tu nie
|
||||
include c.scm end
|
||||
atmo 0 0 0 10 2000 0 0 0 0 endatmo
|
||||
config hiddenevents 1 endconfig
|
||||
time 8:30 0 0 endtime
|
||||
""";
|
||||
|
||||
var (written, rest) = Split(Rewritten(scn));
|
||||
|
||||
Assert.Contains("hiddenevents 1", written);
|
||||
foreach (string kept in new[]
|
||||
{
|
||||
"// atmo: tu byla mgla", "include a.scm end", "{{0}}", "/* config */",
|
||||
"include b.scm end", "{{1}}", "// time 8 tu nie", "include c.scm end"
|
||||
})
|
||||
Assert.Contains(kept, rest);
|
||||
foreach (string gone in new[] { "endatmo", "endconfig", "endtime" })
|
||||
Assert.DoesNotContain(gone, rest);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/* config a 1 endconfig */")]
|
||||
[InlineData("// config a 1 endconfig")]
|
||||
[InlineData("/* atmo 0 0 0 1 2 0 0 0 0 endatmo */")]
|
||||
[InlineData("// atmo 0 0 0 1 2 0 0 0 0 endatmo")]
|
||||
[InlineData("/* time 8:30 0 0 endtime */")]
|
||||
[InlineData("// time 8:30 0 0 endtime")]
|
||||
[InlineData("// movelight 100")]
|
||||
[InlineData("/*\nmovelight 100\n*/")]
|
||||
[InlineData("// scenario.weather.temperature 5")]
|
||||
[InlineData("/* scenario.time.override 6:00 */")]
|
||||
public void What_is_commented_out_stays_as_it_is(string comment)
|
||||
{
|
||||
var (written, rest) = Split(Rewritten($"x\n{comment}\ny\n"));
|
||||
|
||||
Assert.Equal($"x\n{comment}\ny\n", rest);
|
||||
Assert.DoesNotContain("a 1", written);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_comment_in_a_block_stays_when_the_block_goes()
|
||||
{
|
||||
const string scn = "atmo 0 0 0 // mgla\n10 2000 0 0 0 /* zachmurzenie */ 0 endatmo\n" +
|
||||
"config\n// uwaga\nmovelight 5 // lato\nhiddenevents 1\nendconfig\ninclude a.scm end\n";
|
||||
|
||||
var (written, rest) = Split(Rewritten(scn));
|
||||
|
||||
foreach (string comment in new[] { "// mgla\n", "/* zachmurzenie */", "// uwaga\n", "// lato\n" })
|
||||
Assert.Contains(comment, rest);
|
||||
foreach (string word in new[] { "atmo", "2000", "config", "movelight", "hiddenevents" })
|
||||
Assert.DoesNotContain(word, Code(rest));
|
||||
Assert.Contains("hiddenevents 1\r\n", written);
|
||||
Assert.Contains("include a.scm end", rest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Settings_of_every_config_are_carried_and_only_the_starters_own_are_replaced()
|
||||
{
|
||||
const string scn = "config movelight 5 hiddenevents 1 endconfig\nCONFIG\nscenario.weather.temperature 3 pause no\nENDCONFIG\n";
|
||||
|
||||
var (written, rest) = Split(Rewritten(scn));
|
||||
|
||||
Assert.Contains("hiddenevents 1\r\n", written);
|
||||
Assert.Contains("pause no\r\n", written);
|
||||
Assert.DoesNotContain("movelight 5", written);
|
||||
Assert.DoesNotContain("temperature 3", written);
|
||||
Assert.Equal("", rest.Trim());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_word_in_an_include_s_path_is_no_keyword()
|
||||
{
|
||||
const string scn = """
|
||||
include atmo/lasy.scm end
|
||||
include time/8.scm end
|
||||
include config/a.scm end
|
||||
atmo 0 0 0 10 2000 0 0 0 0 endatmo
|
||||
config hiddenevents 1 endconfig
|
||||
""";
|
||||
|
||||
var (_, rest) = Split(Rewritten(scn));
|
||||
|
||||
Assert.Contains("include atmo/lasy.scm end", rest);
|
||||
Assert.Contains("include time/8.scm end", rest);
|
||||
Assert.Contains("include config/a.scm end", rest);
|
||||
Assert.DoesNotContain("endatmo", rest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Weather_in_comments_is_not_read()
|
||||
{
|
||||
const string scn = """
|
||||
// movelight 7
|
||||
// scenario.weather.temperature 40
|
||||
// scenario.time.override 3:33
|
||||
/* time 4:44 0 0 endtime */
|
||||
// atmo 5 mgla w l61
|
||||
include l61/a.scm end
|
||||
movelight 100
|
||||
atmo 0 0 0 50 900 0 0 0 0.7 endatmo
|
||||
""";
|
||||
|
||||
var weather = TestScenery.Parser(new DateTime(2026, 5, 4, 17, 42, 0)).Parse("scenery/x.scn", scn).Weather;
|
||||
|
||||
Assert.Equal(100, weather.Day);
|
||||
Assert.NotEqual(40, weather.Temperature);
|
||||
Assert.Equal("17:42", weather.ScenarioTimeOverride);
|
||||
Assert.Equal(50, weather.FogStart);
|
||||
Assert.Equal(0.7, weather.Overcast);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user