Initial commit

This commit is contained in:
github-classroom[bot] 2025-06-03 15:18:01 +00:00 committed by GitHub
commit 4b1294b32d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 5134 additions and 0 deletions

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

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

View 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>

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