Files
StarterNG/StarterNG.Tests/SceneryTrainsetSaveTests.cs
maj00r 69aa075bfa 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.
2026-11-01 16:06:15 +01:00

579 lines
24 KiB
C#

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"));
}
}