Initial commit
This commit is contained in:
commit
4b1294b32d
23 changed files with 5134 additions and 0 deletions
233
MathInterpreter.Test/ExpressionScannerTests.cs
Normal file
233
MathInterpreter.Test/ExpressionScannerTests.cs
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
using MathInterpreter.Core;
|
||||
|
||||
namespace MathInterpreter.Test;
|
||||
|
||||
public sealed class ExpressionScannerTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("3abc", 3, 1, "abc")]
|
||||
[InlineData("35gh", 35, 2, "gh")]
|
||||
[InlineData("3afc2", 3, 1, "afc2")]
|
||||
[InlineData(" 3abc", 3, 1, "abc")]
|
||||
[InlineData("2 3abc", 23, 2, "abc")]
|
||||
[InlineData("2 3 abc", 23, 2, "abc")]
|
||||
[InlineData("78944", 78944, 5, "")]
|
||||
[InlineData("12?!", 12, 2, "?!")]
|
||||
public void ScanNumber_Simple_Unsigned(string expression, int expectedValue, int expectedLength,
|
||||
string expectedRemainingExpression)
|
||||
{
|
||||
ScanResult<int> result = ExpressionScanner.ScanNumber(expression.AsSpan(), false);
|
||||
|
||||
result.Value.Should().Be(expectedValue);
|
||||
result.Length.Should().Be(expectedLength);
|
||||
result.RemainingExpression.ToString().Should().Be(expectedRemainingExpression);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("+3abc", 3, 1, "abc")]
|
||||
[InlineData("+5ab3c", 5, 1, "ab3c")]
|
||||
[InlineData("+356abc", 356, 3, "abc")]
|
||||
[InlineData("-3abc", -3, 1, "abc")]
|
||||
[InlineData("-8ab2c", -8, 1, "ab2c")]
|
||||
[InlineData("-37abc", -37, 2, "abc")]
|
||||
[InlineData(" - 3abc", -3, 1, "abc")]
|
||||
[InlineData("+ 3abc", 3, 1, "abc")]
|
||||
public void ScanNumber_Simple_Signed(string expression, int expectedValue, int expectedLength,
|
||||
string expectedRemainingExpression)
|
||||
{
|
||||
ScanResult<int> result = ExpressionScanner.ScanNumber(expression.AsSpan(), true);
|
||||
|
||||
result.Value.Should().Be(expectedValue);
|
||||
result.Length.Should().Be(expectedLength);
|
||||
result.RemainingExpression.ToString().Should().Be(expectedRemainingExpression);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("cba")]
|
||||
[InlineData("c1b2a3")]
|
||||
[InlineData(" t")]
|
||||
[InlineData(" t2")]
|
||||
public void ScanNumber_NoDigits(string invalidExpression)
|
||||
{
|
||||
var action = BuildScanNumberWrapper(invalidExpression, false);
|
||||
|
||||
action.Should().Throw<NumberFormatException>()
|
||||
.WithMessage($"Unable to read number from beginning of '{invalidExpression}'");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("+56")]
|
||||
[InlineData("-90")]
|
||||
public void ScanNumber_SignNotAllowed(string invalidExpression)
|
||||
{
|
||||
var action = BuildScanNumberWrapper(invalidExpression, false);
|
||||
|
||||
action.Should().Throw<NumberFormatException>()
|
||||
.WithMessage($"Unable to read number from beginning of '{invalidExpression}' " +
|
||||
"(Sign is not allowed here)");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("++56")]
|
||||
[InlineData("+-56")]
|
||||
[InlineData("--90")]
|
||||
[InlineData("-+90")]
|
||||
public void ScanNumber_MultipleSignNotAllowed(string invalidExpression)
|
||||
{
|
||||
var action = BuildScanNumberWrapper(invalidExpression, true);
|
||||
|
||||
action.Should().Throw<NumberFormatException>()
|
||||
.WithMessage($"Unable to read number from beginning of '{invalidExpression}' " +
|
||||
"(Sign may occur only once)");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("a+56")]
|
||||
[InlineData("b-56")]
|
||||
[InlineData("hello")]
|
||||
[InlineData("-")]
|
||||
[InlineData("+")]
|
||||
[InlineData(" ")]
|
||||
[InlineData(" a ")]
|
||||
[InlineData(" a6 ")]
|
||||
[InlineData("?1")]
|
||||
[InlineData("")]
|
||||
public void ScanNumber_Invalid(string invalidExpression)
|
||||
{
|
||||
var action = BuildScanNumberWrapper(invalidExpression, true);
|
||||
|
||||
action.Should().Throw<NumberFormatException>()
|
||||
.WithMessage($"Unable to read number from beginning of '{invalidExpression}'");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("+", Operator.Plus, "")]
|
||||
[InlineData("-abc", Operator.Minus, "abc")]
|
||||
[InlineData("* abc", Operator.Multiply, " abc")]
|
||||
[InlineData("/abc ", Operator.Divide, "abc ")]
|
||||
public void ScanOperator_Simple(string expression, Operator expectedOperator, string remainingExpression)
|
||||
{
|
||||
ScanResult<Operator> result = ExpressionScanner.ScanOperator(expression);
|
||||
|
||||
result.Value.Should().Be(expectedOperator);
|
||||
result.Length.Should().Be(1);
|
||||
result.RemainingExpression.ToString().Should().Be(remainingExpression);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("&")]
|
||||
[InlineData("abc")]
|
||||
[InlineData(" abc")]
|
||||
[InlineData("g /")]
|
||||
public void ScanOperator_Invalid(string expression)
|
||||
{
|
||||
var action = BuildScanOperatorWrapper(expression);
|
||||
|
||||
action.Should().Throw<OperatorException>()
|
||||
.WithMessage($"Unknown operator: {expression.TrimStart()[0]}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanOperator_Empty()
|
||||
{
|
||||
var action = BuildScanOperatorWrapper(string.Empty);
|
||||
|
||||
action.Should().Throw<OperatorException>().WithMessage("Empty expression");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("123abc", 123, 3, "abc")]
|
||||
[InlineData("23.49cba", 23.49, 5, "cba")]
|
||||
[InlineData("0.45ab2c", 0.45, 4, "ab2c")]
|
||||
[InlineData("0.452ab1c", 0.452, 5, "ab1c")]
|
||||
public void ScanOperand_Simple(string expression, double expectedValue, int expectedLength,
|
||||
string remainingExpression)
|
||||
{
|
||||
ScanResult<double> result = ExpressionScanner.ScanOperand(expression);
|
||||
|
||||
result.Value.Should().BeApproximately(expectedValue, double.Epsilon);
|
||||
result.Length.Should().Be(expectedLength);
|
||||
result.RemainingExpression.ToString().Should().Be(remainingExpression);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("+123", 123, 3, "")]
|
||||
[InlineData("+123abc", 123, 3, "abc")]
|
||||
[InlineData("+123.12abc", 123.12, 6, "abc")]
|
||||
[InlineData("-123", -123, 3, "")]
|
||||
[InlineData("-123cb", -123, 3, "cb")]
|
||||
[InlineData("-123.89cb", -123.89, 6, "cb")]
|
||||
public void ScanOperand_Simple_Signed(string expression, double expectedValue, int expectedLength,
|
||||
string remainingExpression)
|
||||
{
|
||||
ScanResult<double> result = ExpressionScanner.ScanOperand(expression);
|
||||
|
||||
result.Value.Should().BeApproximately(expectedValue, double.Epsilon);
|
||||
result.Length.Should().Be(expectedLength);
|
||||
result.RemainingExpression.ToString().Should().Be(remainingExpression);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("abc")]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData("cb43")]
|
||||
public void ScanOperand_Invalid_NoNumber(string expression)
|
||||
{
|
||||
BuildScanOperandWrapper(expression).Should()
|
||||
.Throw<ExpressionFormatException>()
|
||||
.WithMessage("Operand is missing");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(".12")]
|
||||
[InlineData(" .12")]
|
||||
[InlineData(".12.55")]
|
||||
public void ScanOperand_Invalid_Integral(string expression)
|
||||
{
|
||||
BuildScanOperandWrapper(expression).Should()
|
||||
.Throw<ExpressionFormatException>()
|
||||
.WithMessage("Unable to parse integral part of number");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("123.a")]
|
||||
[InlineData("123. ")]
|
||||
[InlineData("123. b")]
|
||||
[InlineData("123.g5")]
|
||||
[InlineData("123.-5")]
|
||||
[InlineData("123.+5")]
|
||||
public void ScanOperand_Invalid_Fractional(string expression)
|
||||
{
|
||||
BuildScanOperandWrapper(expression).Should()
|
||||
.Throw<ExpressionFormatException>()
|
||||
.WithMessage("Unable to parse fractional part of number");
|
||||
}
|
||||
|
||||
private static Action BuildScanNumberWrapper(string expression, bool allowSign)
|
||||
{
|
||||
return () =>
|
||||
{
|
||||
ReadOnlySpan<char> span = expression.AsSpan();
|
||||
ExpressionScanner.ScanNumber(span, allowSign);
|
||||
};
|
||||
}
|
||||
|
||||
private static Action BuildScanOperandWrapper(string expression)
|
||||
{
|
||||
return () =>
|
||||
{
|
||||
ReadOnlySpan<char> span = expression.AsSpan();
|
||||
ExpressionScanner.ScanOperand(span);
|
||||
};
|
||||
}
|
||||
|
||||
private static Action BuildScanOperatorWrapper(string expression)
|
||||
{
|
||||
return () =>
|
||||
{
|
||||
ReadOnlySpan<char> span = expression.AsSpan();
|
||||
ExpressionScanner.ScanOperator(span);
|
||||
};
|
||||
}
|
||||
}
|
||||
63
MathInterpreter.Test/MathExpressionTests.cs
Normal file
63
MathInterpreter.Test/MathExpressionTests.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
using MathInterpreter.Core;
|
||||
|
||||
namespace MathInterpreter.Test;
|
||||
|
||||
public sealed class MathExpressionTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("123 + 45")]
|
||||
[InlineData("123+ 45")]
|
||||
[InlineData("41 - 12")]
|
||||
[InlineData("41 -12")]
|
||||
[InlineData("12 * 0.9")]
|
||||
[InlineData("12*0.9")]
|
||||
[InlineData("2.32 / -3.9198")]
|
||||
public void MathExpression_Construction_Valid(string expression)
|
||||
{
|
||||
var action = () =>
|
||||
{
|
||||
var _ = new MathExpression(expression);
|
||||
};
|
||||
|
||||
action.Should().NotThrow("Valid expression passed, don't expect any exceptions");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("123 + ", "Right operand is missing")]
|
||||
[InlineData("+ 456", "Operator is missing")]
|
||||
[InlineData("123 / 0", "Division by zero is not allowed")]
|
||||
[InlineData("123 abc 456", "Unknown operator: a")]
|
||||
public void MathExpression_Construction_Invalid(string expression, string expectedExceptionMessage)
|
||||
{
|
||||
var action = () =>
|
||||
{
|
||||
var _ = new MathExpression(expression);
|
||||
};
|
||||
|
||||
action.Should().Throw<ExpressionException>().WithMessage(expectedExceptionMessage);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("2 + 2", 4.0)]
|
||||
[InlineData("10 - -3", 13.0)]
|
||||
[InlineData("5.2 * 2", 10.4)]
|
||||
[InlineData("-8 / 2", -4.0)]
|
||||
public void Result_Valid(string expression, double expectedResult)
|
||||
{
|
||||
var mathExpression = new MathExpression(expression);
|
||||
|
||||
mathExpression.Result.Should().Be(expectedResult);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("2 + 2aa", "2 + 2")]
|
||||
[InlineData("10 - 3", "10 - 3")]
|
||||
[InlineData("5 * 2f", "5 * 2")]
|
||||
[InlineData(" 8 / 2", "8 / 2")]
|
||||
public void StringRepresentation(string expression, string expectedString)
|
||||
{
|
||||
var mathExpression = new MathExpression(expression);
|
||||
|
||||
mathExpression.ToString().Should().Be(expectedString);
|
||||
}
|
||||
}
|
||||
36
MathInterpreter.Test/MathInterpreter.Test.csproj
Normal file
36
MathInterpreter.Test/MathInterpreter.Test.csproj
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="FluentAssertions" />
|
||||
<Using Include="Xunit" />
|
||||
<Using Include="NSubstitute" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AwesomeAssertions" Version="8.1.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MathInterpreter\MathInterpreter.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
32
MathInterpreter.Test/ScanResultTests.cs
Normal file
32
MathInterpreter.Test/ScanResultTests.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using MathInterpreter.Core;
|
||||
|
||||
namespace MathInterpreter.Test;
|
||||
|
||||
public sealed class ScanResultTests
|
||||
{
|
||||
[Fact]
|
||||
public void ScanResult_Construction_Valid()
|
||||
{
|
||||
const int ExpectedValue = -123;
|
||||
const int ExpectedLength = 3;
|
||||
const string ExpectedRemainingExpression = "abc";
|
||||
|
||||
var result = new ScanResult<int>(ExpectedValue, ExpectedLength, ExpectedRemainingExpression);
|
||||
|
||||
result.Value.Should().Be(ExpectedValue);
|
||||
result.Length.Should().Be(ExpectedLength);
|
||||
result.RemainingExpression.ToString().Should().Be(ExpectedRemainingExpression);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScanResult_Construction_Invalid()
|
||||
{
|
||||
var action = () =>
|
||||
{
|
||||
var _ = new ScanResult<int>(1, -2, string.Empty);
|
||||
};
|
||||
|
||||
action.Should().Throw<ArgumentOutOfRangeException>()
|
||||
.WithMessage("Length must be non-negative (Parameter 'length')");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue