466 lines
No EOL
15 KiB
C#
466 lines
No EOL
15 KiB
C#
using System.Reflection;
|
|
using FluentAssertions;
|
|
using Xunit;
|
|
|
|
namespace BucketChain.Test;
|
|
|
|
public sealed class PersonTests
|
|
{
|
|
[Fact]
|
|
public void Construction()
|
|
{
|
|
var person = new Person();
|
|
|
|
person.LeftNeighbor.Should().BeNull("not set yet");
|
|
person.RightNeighbor.Should().BeNull("not set yet");
|
|
person.HasBucket.Should().BeFalse("no bucket received yet");
|
|
person.LastStepPerformed.Should().Be(0, "performed in no step yet");
|
|
}
|
|
|
|
[Fact]
|
|
public void JoinChain_OneOther_Right()
|
|
{
|
|
var firstPerson = new Person();
|
|
var newPerson = new Person();
|
|
|
|
newPerson.JoinChain(firstPerson, true);
|
|
|
|
firstPerson.LeftNeighbor.Should().BeNull();
|
|
firstPerson.RightNeighbor.Should().BeSameAs(newPerson, "joined on right side");
|
|
newPerson.LeftNeighbor.Should().BeSameAs(firstPerson);
|
|
newPerson.RightNeighbor.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void JoinChain_OneOther_Left()
|
|
{
|
|
var firstPerson = new Person();
|
|
var newPerson = new Person();
|
|
|
|
newPerson.JoinChain(firstPerson, false);
|
|
|
|
firstPerson.LeftNeighbor.Should().BeSameAs(newPerson, "joined on left side");
|
|
firstPerson.RightNeighbor.Should().BeNull();
|
|
newPerson.LeftNeighbor.Should().BeNull();
|
|
newPerson.RightNeighbor.Should().BeSameAs(firstPerson);
|
|
}
|
|
|
|
[Fact]
|
|
public void JoinChain_TwoOthers_Left()
|
|
{
|
|
var (firstPerson, secondPerson) = CreateMinimalChain();
|
|
var newPerson = new Person();
|
|
|
|
newPerson.JoinChain(firstPerson, false);
|
|
|
|
firstPerson.LeftNeighbor.Should().BeSameAs(newPerson, "joined on left side of first person");
|
|
firstPerson.RightNeighbor.Should().BeSameAs(secondPerson, "unchanged");
|
|
secondPerson.LeftNeighbor.Should().BeSameAs(firstPerson, "unchanged");
|
|
secondPerson.RightNeighbor.Should().BeNull();
|
|
newPerson.LeftNeighbor.Should().BeNull();
|
|
newPerson.RightNeighbor.Should().BeSameAs(firstPerson);
|
|
}
|
|
|
|
[Fact]
|
|
public void JoinChain_TwoOthers_Right()
|
|
{
|
|
var (firstPerson, secondPerson) = CreateMinimalChain();
|
|
var newPerson = new Person();
|
|
|
|
newPerson.JoinChain(secondPerson, true);
|
|
|
|
firstPerson.LeftNeighbor.Should().BeNull();
|
|
firstPerson.RightNeighbor.Should().BeSameAs(secondPerson, "unchanged");
|
|
secondPerson.LeftNeighbor.Should().BeSameAs(firstPerson, "unchanged");
|
|
secondPerson.RightNeighbor.Should().BeSameAs(newPerson, "joined on right side of second person");
|
|
newPerson.LeftNeighbor.Should().BeSameAs(secondPerson);
|
|
newPerson.RightNeighbor.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void JoinChain_TwoOthers_Center_Option1()
|
|
{
|
|
var (firstPerson, secondPerson) = CreateMinimalChain();
|
|
var newPerson = new Person();
|
|
|
|
newPerson.JoinChain(firstPerson, true);
|
|
|
|
firstPerson.LeftNeighbor.Should().BeNull();
|
|
firstPerson.RightNeighbor.Should().BeSameAs(newPerson, "joined on right side of first person");
|
|
secondPerson.LeftNeighbor.Should().BeSameAs(newPerson, "joined on left side of second person");
|
|
secondPerson.RightNeighbor.Should().BeNull();
|
|
newPerson.LeftNeighbor.Should().BeSameAs(firstPerson);
|
|
newPerson.RightNeighbor.Should().BeSameAs(secondPerson);
|
|
}
|
|
|
|
[Fact]
|
|
public void JoinChain_TwoOthers_Center_Option2()
|
|
{
|
|
var (firstPerson, secondPerson) = CreateMinimalChain();
|
|
var newPerson = new Person();
|
|
|
|
newPerson.JoinChain(secondPerson, false);
|
|
|
|
firstPerson.LeftNeighbor.Should().BeNull();
|
|
firstPerson.RightNeighbor.Should().BeSameAs(newPerson, "joined on right side of first person");
|
|
secondPerson.LeftNeighbor.Should().BeSameAs(newPerson, "joined on left side of second person");
|
|
secondPerson.RightNeighbor.Should().BeNull();
|
|
newPerson.LeftNeighbor.Should().BeSameAs(firstPerson);
|
|
newPerson.RightNeighbor.Should().BeSameAs(secondPerson);
|
|
}
|
|
|
|
[Fact]
|
|
public void FightFire_Simple()
|
|
{
|
|
const double FireInitialSize = 45.67D;
|
|
const double BucketSize = 2.4D;
|
|
|
|
var fire = new Fire(FireInitialSize, default(double));
|
|
var bucket = CreateFilledBucket(BucketSize);
|
|
var person = new Person();
|
|
SetBucket(person, bucket, true);
|
|
|
|
person.FightFire(fire);
|
|
|
|
fire.FireSize.Should().BeApproximately(FireInitialSize - BucketSize,
|
|
double.Epsilon, "fire shrank by amount of water in the bucket");
|
|
bucket.IsEmpty.Should().BeTrue("bucket content has been used");
|
|
}
|
|
|
|
[Fact]
|
|
public void FightFire_NoBucket()
|
|
{
|
|
const double FireInitialSize = 45.67D;
|
|
|
|
var fire = new Fire(FireInitialSize, default(double));
|
|
var person = new Person();
|
|
|
|
person.FightFire(fire);
|
|
|
|
fire.FireSize.Should().BeApproximately(FireInitialSize,
|
|
double.Epsilon, "fire size did not change");
|
|
}
|
|
|
|
[Fact]
|
|
public void UseWell_ExistingBucket_Forward()
|
|
{
|
|
const double WellInitialFillLevel = 20D;
|
|
const double BucketSize = 1.86D;
|
|
|
|
var well = new Well(WellInitialFillLevel, default(double));
|
|
var bucket = new Bucket(BucketSize);
|
|
var person = new Person();
|
|
SetBucket(person, bucket, true);
|
|
|
|
person.UseWell(well, null);
|
|
|
|
bucket.IsEmpty.Should().BeFalse("has been filled");
|
|
well.LitersRemaining.Should().BeApproximately(WellInitialFillLevel - BucketSize,
|
|
double.Epsilon, "well level has been reduced by bucket amount");
|
|
}
|
|
|
|
[Fact]
|
|
public void UseWell_ExistingBucket_Backward()
|
|
{
|
|
const double WellInitialFillLevel = 20D;
|
|
const double BucketSize = 1.86D;
|
|
|
|
var well = new Well(WellInitialFillLevel, default(double));
|
|
var bucket = new Bucket(BucketSize);
|
|
var person = new Person();
|
|
SetBucket(person, bucket, false);
|
|
|
|
person.UseWell(well, null);
|
|
|
|
bucket.IsEmpty.Should().BeFalse("has been filled");
|
|
well.LitersRemaining.Should().BeApproximately(WellInitialFillLevel - BucketSize,
|
|
double.Epsilon, "well level has been reduced by bucket amount");
|
|
}
|
|
|
|
[Fact]
|
|
public void UseWell_NoBucket()
|
|
{
|
|
const double WellInitialFillLevel = 20D;
|
|
|
|
var well = new Well(WellInitialFillLevel, default(double));
|
|
var person = new Person();
|
|
|
|
person.UseWell(well, null);
|
|
|
|
well.LitersRemaining.Should().Be(WellInitialFillLevel, "unchanged");
|
|
}
|
|
|
|
[Fact]
|
|
public void UseWell_AdditionalBucket()
|
|
{
|
|
const double WellInitialFillLevel = 20D;
|
|
const double BucketSize = 1.86D;
|
|
|
|
var well = new Well(WellInitialFillLevel, default(double));
|
|
var bucket = new Bucket(BucketSize);
|
|
var person = new Person();
|
|
|
|
person.UseWell(well, bucket);
|
|
|
|
bucket.IsEmpty.Should().BeFalse("has been filled");
|
|
well.LitersRemaining.Should().BeApproximately(WellInitialFillLevel - BucketSize,
|
|
double.Epsilon, "well level has been reduced by bucket amount");
|
|
}
|
|
|
|
[Fact]
|
|
public void MoveBucket_Forward_Simple()
|
|
{
|
|
var (first, second) = CreateMinimalChain();
|
|
var bucket = CreateFilledBucket(1.2D);
|
|
SetBucket(first, bucket, true);
|
|
|
|
HasBucket(first, true).Should().BeTrue();
|
|
HasBucket(second, true).Should().BeFalse();
|
|
first.MoveBucket(1)
|
|
.Should().BeTrue("successfully moved forward to next person");
|
|
HasBucket(first, true).Should().BeFalse("sent forward");
|
|
HasBucket(second, true).Should().BeTrue("received");
|
|
HasBucket(first, false).Should().BeFalse();
|
|
HasBucket(second, false).Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void MoveBucket_Backward_Simple()
|
|
{
|
|
var (first, second) = CreateMinimalChain();
|
|
var bucket = new Bucket(1.2D);
|
|
SetBucket(second, bucket, false);
|
|
|
|
HasBucket(first, true).Should().BeFalse();
|
|
HasBucket(second, true).Should().BeFalse();
|
|
HasBucket(second, false).Should().BeTrue();
|
|
first.MoveBucket(1)
|
|
.Should().BeTrue("successfully moved backward from next person");
|
|
HasBucket(first, true).Should().BeFalse();
|
|
HasBucket(second, true).Should().BeFalse();
|
|
HasBucket(first, false).Should().BeTrue("received");
|
|
HasBucket(second, false).Should().BeFalse("sent backward");
|
|
}
|
|
|
|
[Fact]
|
|
public void MoveBucket_Forward_And_Backward()
|
|
{
|
|
var (first, second) = CreateMinimalChain();
|
|
var bucket = CreateFilledBucket(1.2D);
|
|
var backwardBucket = new Bucket(1.2D);
|
|
SetBucket(first, bucket, true);
|
|
SetBucket(second, backwardBucket, false);
|
|
|
|
HasBucket(first, true).Should().BeTrue();
|
|
HasBucket(second, true).Should().BeFalse();
|
|
HasBucket(first, false).Should().BeFalse();
|
|
HasBucket(second, false).Should().BeTrue();
|
|
first.MoveBucket(1)
|
|
.Should().BeTrue("successfully moved forward to and backward from next person");
|
|
HasBucket(first, true).Should().BeFalse("sent forward");
|
|
HasBucket(second, true).Should().BeTrue("received");
|
|
HasBucket(first, false).Should().BeTrue("received");
|
|
HasBucket(second, false).Should().BeFalse("sent backward");
|
|
}
|
|
|
|
[Fact]
|
|
public void MoveBucket_NoBuckets()
|
|
{
|
|
var (first, second) = CreateMinimalChain();
|
|
|
|
HasBucket(first, true).Should().BeFalse();
|
|
HasBucket(second, true).Should().BeFalse();
|
|
HasBucket(first, false).Should().BeFalse();
|
|
HasBucket(second, false).Should().BeFalse();
|
|
first.MoveBucket(1)
|
|
.Should().BeTrue("nothing was moved, but there was no error either");
|
|
HasBucket(first, true).Should().BeFalse();
|
|
HasBucket(second, true).Should().BeFalse();
|
|
HasBucket(first, false).Should().BeFalse();
|
|
HasBucket(second, false).Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void MoveBucket_ForwardBlocked()
|
|
{
|
|
var (first, second) = CreateMinimalChain();
|
|
var bucket1 = CreateFilledBucket(1.2D);
|
|
var bucket2 = CreateFilledBucket(1.2D);
|
|
SetBucket(first, bucket1, true);
|
|
SetBucket(second, bucket2, true);
|
|
|
|
HasBucket(first, true).Should().BeTrue();
|
|
HasBucket(second, true).Should().BeTrue();
|
|
first.MoveBucket(1)
|
|
.Should().BeTrue("nothing was moved, but there was no error either");
|
|
HasBucket(first, true).Should().BeTrue();
|
|
HasBucket(second, true).Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void MoveBucket_BackwardBlocked()
|
|
{
|
|
var (first, second) = CreateMinimalChain();
|
|
var bucket1 = CreateFilledBucket(1.2D);
|
|
var bucket2 = CreateFilledBucket(1.2D);
|
|
SetBucket(first, bucket1, false);
|
|
SetBucket(second, bucket2, false);
|
|
|
|
HasBucket(first, false).Should().BeTrue();
|
|
HasBucket(second, false).Should().BeTrue();
|
|
first.MoveBucket(1)
|
|
.Should().BeTrue("nothing was moved, but there was no error either");
|
|
HasBucket(first, false).Should().BeTrue();
|
|
HasBucket(second, false).Should().BeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void MoveBucket_AlreadyPerformedInStep_Simple()
|
|
{
|
|
const int Step = 12;
|
|
|
|
var person = new Person();
|
|
SetLastStepPerformed(person, Step);
|
|
|
|
person.MoveBucket(Step)
|
|
.Should().BeFalse("already performed in this step");
|
|
}
|
|
|
|
[Fact]
|
|
public void MoveBucket_AlreadyPerformedInStep_Complex()
|
|
{
|
|
const int Step = 12;
|
|
|
|
var (first, second) = CreateMinimalChain();
|
|
var third = new Person();
|
|
third.JoinChain(second, true);
|
|
SetBucket(first, CreateFilledBucket(1.2D), true);
|
|
|
|
first.MoveBucket(Step)
|
|
.Should().BeTrue("can move");
|
|
second.MoveBucket(Step)
|
|
.Should().BeFalse("already performed in this step");
|
|
}
|
|
|
|
[Fact]
|
|
public void MoveBucket_NoNeighbor()
|
|
{
|
|
var person = new Person();
|
|
SetBucket(person, CreateFilledBucket(1.2D), true);
|
|
|
|
person.MoveBucket(1)
|
|
.Should().BeFalse("no right neighbor to move to/from");
|
|
}
|
|
|
|
[Theory]
|
|
[MemberData(nameof(StringRepresentationData))]
|
|
public void StringRepresentation(Person person, string expected, string reason)
|
|
{
|
|
person.ToString()
|
|
.Should().Be(expected, reason);
|
|
}
|
|
|
|
public static TheoryData<Person, string, string> StringRepresentationData()
|
|
{
|
|
return new()
|
|
{
|
|
{
|
|
CreatePerson(null),
|
|
"🫲",
|
|
"has no bucket"
|
|
},
|
|
{
|
|
CreatePerson(false),
|
|
"🔘",
|
|
"has empty forward bucket only"
|
|
},
|
|
{
|
|
CreatePerson(false, false),
|
|
"🔘",
|
|
"has empty backward bucket only"
|
|
},
|
|
{
|
|
CreatePerson(true),
|
|
"🔵",
|
|
"has full forward bucket only"
|
|
},
|
|
{
|
|
CreatePerson(true, false),
|
|
"🔵",
|
|
"has full backward bucket only"
|
|
},
|
|
{
|
|
CreatePerson(false,
|
|
then: p => SetBucket(p, CreateFilledBucket(1), false)),
|
|
"🔘",
|
|
"has empty forward bucket and full backward bucket"
|
|
},
|
|
{
|
|
CreatePerson(true,
|
|
then: p => SetBucket(p, new Bucket(1D), false)),
|
|
"🔵",
|
|
"has full forward bucket and empty backward bucket"
|
|
}
|
|
};
|
|
|
|
static Person CreatePerson(bool? bucketFull, bool forwardBucket = true, Action<Person>? then = null)
|
|
{
|
|
var person = new Person();
|
|
if (bucketFull == null)
|
|
{
|
|
return person;
|
|
}
|
|
|
|
var bucket = bucketFull.Value ? CreateFilledBucket(1D) : new Bucket(1D);
|
|
SetBucket(person, bucket, forwardBucket);
|
|
then?.Invoke(person);
|
|
|
|
return person;
|
|
}
|
|
}
|
|
|
|
private static (Person FirstPerson, Person SecondPerson) CreateMinimalChain()
|
|
{
|
|
var firstPerson = new Person();
|
|
var secondPerson = new Person();
|
|
|
|
secondPerson.JoinChain(firstPerson, true);
|
|
|
|
return (firstPerson, secondPerson);
|
|
}
|
|
|
|
private static Bucket CreateFilledBucket(double size)
|
|
{
|
|
var bucket = new Bucket(size);
|
|
bucket.Fill(size);
|
|
|
|
return bucket;
|
|
}
|
|
|
|
#region Test Helper - ignore
|
|
|
|
private static void SetBucket(Person person, Bucket? bucket, bool forwardBucket)
|
|
{
|
|
var field = GetBucketField(forwardBucket);
|
|
field.SetValue(person, bucket);
|
|
}
|
|
|
|
private static bool HasBucket(Person person, bool forwardBucket)
|
|
{
|
|
var field = GetBucketField(forwardBucket);
|
|
return field.GetValue(person) != null;
|
|
}
|
|
|
|
private static FieldInfo GetBucketField(bool forwardBucket) =>
|
|
typeof(Person).GetField(forwardBucket ? "_forwardBucket" : "_backwardBucket",
|
|
BindingFlags.Instance | BindingFlags.NonPublic)!;
|
|
|
|
private static void SetLastStepPerformed(Person person, int step)
|
|
{
|
|
var prop = typeof(Person).GetProperty("LastStepPerformed",
|
|
BindingFlags.Instance | BindingFlags.Public);
|
|
prop!.SetValue(person, step);
|
|
}
|
|
|
|
#endregion
|
|
} |