Skip to content

Commit

Permalink
Merge pull request #3027 from planetarium/test/improve-exception-test
Browse files Browse the repository at this point in the history
test: improve exception serialization test
  • Loading branch information
boscohyun authored Nov 20, 2024
2 parents f2951f4 + f7c8303 commit 8dda982
Show file tree
Hide file tree
Showing 21 changed files with 371 additions and 181 deletions.
305 changes: 222 additions & 83 deletions .Lib9c.Tests/Action/ExceptionTest.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
namespace Lib9c.Tests.Action
{
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using Bencodex.Types;
using Lib9c.Formatters;
using Libplanet.Action;
using Libplanet.Blockchain.Renderers.Debug;
using Libplanet.Common;
using Libplanet.Crypto;
using Libplanet.Types.Assets;
using Libplanet.Types.Blocks;
using Libplanet.Types.Evidence;
using Libplanet.Types.Tx;
using MessagePack;
using MessagePack.Resolvers;
using Nekoyume;
using Nekoyume.Action;
using Nekoyume.Action.Exceptions;
using Nekoyume.Action.Exceptions.AdventureBoss;
using Nekoyume.Action.Exceptions.Arena;
using Nekoyume.Exceptions;
using Nekoyume.Model.State;
using Nekoyume.TableData;
using Xunit;

public class ExceptionTest
Expand All @@ -28,96 +36,227 @@ public ExceptionTest()
MessagePackSerializer.DefaultOptions = options;
}

[Theory]
[InlineData(typeof(InvalidTradableIdException))]
[InlineData(typeof(AlreadyReceivedException))]
[InlineData(typeof(ArenaNotEndedException))]
[InlineData(typeof(AvatarIndexAlreadyUsedException))]
[InlineData(typeof(FailedLoadStateException))]
[InlineData(typeof(InvalidNamePatternException))]
[InlineData(typeof(CombinationSlotResultNullException))]
[InlineData(typeof(CombinationSlotUnlockException))]
[InlineData(typeof(NotEnoughMaterialException))]
[InlineData(typeof(InvalidPriceException))]
[InlineData(typeof(ItemDoesNotExistException))]
[InlineData(typeof(EquipmentLevelExceededException))]
[InlineData(typeof(DuplicateMaterialException))]
[InlineData(typeof(InvalidMaterialException))]
[InlineData(typeof(ConsumableSlotOutOfRangeException))]
[InlineData(typeof(ConsumableSlotUnlockException))]
[InlineData(typeof(InvalidItemTypeException))]
[InlineData(typeof(InvalidRedeemCodeException))]
[InlineData(typeof(DuplicateRedeemException))]
[InlineData(typeof(SheetRowValidateException))]
[InlineData(typeof(ShopItemExpiredException))]
[InlineData(typeof(InvalidMonsterCollectionRoundException))]
[InlineData(typeof(MonsterCollectionExpiredException))]
[InlineData(typeof(InvalidLevelException))]
[InlineData(typeof(ActionPointExceededException))]
[InlineData(typeof(InvalidItemCountException))]
[InlineData(typeof(DuplicateOrderIdException))]
[InlineData(typeof(OrderIdDoesNotExistException))]
[InlineData(typeof(ActionObsoletedException))]
[InlineData(typeof(FailedLoadSheetException))]
[InlineData(typeof(InvalidEquipmentException))]
[InlineData(typeof(AlreadyRecipeUnlockedException))]
[InlineData(typeof(InvalidRecipeIdException))]
[InlineData(typeof(AlreadyWorldUnlockedException))]
[InlineData(typeof(InvalidActionFieldException))]
[InlineData(typeof(NotEnoughEventDungeonTicketsException))]
[InlineData(typeof(InvalidClaimException))]
[InlineData(typeof(RequiredBlockIntervalException))]
[InlineData(typeof(ActionUnavailableException))]
[InlineData(typeof(InvalidTransferCurrencyException))]
[InlineData(typeof(InvalidCurrencyException))]
[InlineData(typeof(InvalidProductTypeException))]
[InlineData(typeof(ProductNotFoundException))]
[InlineData(typeof(AlreadyContractedException))]
[InlineData(typeof(ItemNotFoundException))]
[InlineData(typeof(NotEnoughItemException))]
[InlineData(typeof(StateNullException))]
[InlineData(typeof(AlreadyClaimedException))]
[InlineData(typeof(ClaimExpiredException))]
[InlineData(typeof(InsufficientStakingException))]
[InlineData(typeof(InvalidAdventureBossSeasonException))]
[InlineData(typeof(InvalidBountyException))]
[InlineData(typeof(MaxInvestmentCountExceededException))]
[InlineData(typeof(PreviousBountyException))]
[InlineData(typeof(SeasonInProgressException))]
[InlineData(typeof(EmptyRewardException))]
[InlineData(typeof(UnsupportedStateException))]
[InlineData(typeof(AlreadyJoinedArenaException))]
public void Exception_Serializable(Type excType)
/// <summary>
/// Get all exceptions defined in the Libplanet namespace.
/// </summary>
/// <returns>Enumerable object array that contain an exception object.</returns>
public static IEnumerable<object[]> GetLibplanetExceptions()
{
if (Activator.CreateInstance(excType, "for testing") is Exception exc)
var t = typeof(Exception);
var exceptions = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(e => e.GetTypes())
.Where(e =>
e.Namespace is not null &&
e.Namespace.StartsWith("Libplanet") &&
!e.IsAbstract &&
e.IsClass &&
e.IsAssignableTo(t))
.ToArray();
foreach (var e in exceptions)
{
AssertException(excType, exc);
if (e == typeof(InvalidBlockProtocolVersionException) ||
e == typeof(InvalidBlockStateRootHashException) ||
e == typeof(DuplicateVoteException))
{
// FIXME:
// MessagePack.MessagePackSerializationException: Failed to serialize System.Exception value.
continue;
}

yield return new object[] { e, };
}
else
}

/// <summary>
/// Get all exceptions defined in the Lib9c namespace.
/// </summary>
/// <returns>Enumerable object array that contain an exception object.</returns>
public static IEnumerable<object[]> GetLib9cExceptions()
{
var t = typeof(Exception);
var exceptions = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(e => e.GetTypes())
.Where(e =>
e.Namespace is not null &&
e.Namespace.StartsWith("Nekoyume") &&
!e.IsAbstract &&
e.IsClass &&
e.IsAssignableTo(t))
.ToArray();
foreach (var e in exceptions)
{
throw new InvalidCastException();
yield return new object[] { e, };
}
}

/// <summary>
/// Libplanet Exception을 수정하기 위한 임시 테스트 코드입니다
/// TODO: Libplanet Exception을 수정하고 테스트 코드 케이스를 추가해야 합니다
/// Tests weather Libplanet and Lib9c exceptions are serializable using MessagePack.
/// </summary>
/// <param name="excType">예외타입.</param>
/// <param name="excType">The type of the exception being tested.</param>
[Theory]
[InlineData(typeof(Libplanet.Action.State.InsufficientBalanceException))]
public void Libplanet_Exception_Serializable(Type excType)
[MemberData(nameof(GetLibplanetExceptions))]
[MemberData(nameof(GetLib9cExceptions))]
public void Exception_Serializable(Type excType)
{
// TODO: 테스트 받는 방식 수정
var customAddress = new Address("399bddF9F7B6d902ea27037B907B2486C9910730");
var customFav = new Libplanet.Types.Assets.FungibleAssetValue(Currencies.Crystal);
if (Activator.CreateInstance(excType, "for testing", customAddress, customFav) is Exception exc)
var constructorTuples = excType.GetConstructors()
.Select(e => (constructorInfo: e, parameters: e.GetParameters()))
.OrderBy(tuple => tuple.parameters.Length);
foreach (var (constructorInfo, parameters) in constructorTuples)
{
AssertException(excType, exc);
var parametersLength = parameters.Length;
if (parametersLength == 0)
{
AssertException((Exception)constructorInfo.Invoke(Array.Empty<object>()));
return;
}

var found = true;
var parameterValues = new List<object>();
for (var i = 0; i < parametersLength; i++)
{
if (TryGetDefaultValue(parameters[i].ParameterType, out var value))
{
parameterValues.Add(value);
}
else
{
found = false;
break;
}
}

if (!found)
{
continue;
}

AssertException((Exception)constructorInfo.Invoke(parameterValues.ToArray()));
return;
}
else

throw new InvalidOperationException($"No suitable constructor found for {excType.FullName}.");

bool TryGetDefaultValue(Type type, out object value)
{
throw new InvalidCastException();
if (Nullable.GetUnderlyingType(type) != null)
{
value = null;
return true;
}

if (type.IsClass)
{
value = null;
return true;
}

if (type == typeof(bool))
{
value = default(bool);
return true;
}

if (type == typeof(int))
{
value = default(int);
return true;
}

if (type == typeof(long))
{
value = default(long);
return true;
}

if (type == typeof(string))
{
value = "for testing";
return true;
}

if (type.IsAssignableTo(typeof(Exception)))
{
value = null;
return true;
}

if (type == typeof(Guid))
{
value = Guid.NewGuid();
return true;
}

if (type == typeof(HashDigest<SHA256>))
{
value = HashDigest<SHA256>.FromString("baa2081d3b485ef2906c95a3965531ec750a74cfaefe91d0c3061865608b426c");
return true;
}

if (type == typeof(ImmutableArray<byte>))
{
value = ImmutableArray<byte>.Empty;
return true;
}

if (type == typeof(IImmutableSet<Type>))
{
value = ImmutableHashSet<Type>.Empty;
return true;
}

if (type == typeof(IReadOnlyList<RenderRecord>))
{
value = new List<RenderRecord>();
return true;
}

if (type == typeof(IAction))
{
value = new DailyReward
{
avatarAddress = Addresses.Agent,
};
return true;
}

if (type == typeof(IValue))
{
value = Bencodex.Types.Null.Value;
return true;
}

if (type == typeof(Address))
{
value = Nekoyume.Addresses.Admin;
return true;
}

if (type == typeof(Currency))
{
value = Currencies.Crystal;
return true;
}

if (type == typeof(FungibleAssetValue))
{
value = FungibleAssetValue.Parse(Currencies.Crystal, "1");
return true;
}

if (type == typeof(BlockHash))
{
value = BlockHash.FromString("4582250d0da33b06779a8475d283d5dd210c683b9b999d74d03fac4f58fa6bce");
return true;
}

if (type == typeof(TxId))
{
value = TxId.FromHex("300826da62b595d8cd663dadf04995a7411534d1cdc17dac75ce88754472f774");
return true;
}

value = null;
return false;
}
}

Expand Down Expand Up @@ -156,10 +295,10 @@ public void AdminPermissionExceptionSerializable()
private static void AssertException<T>(Exception exc)
where T : Exception
{
AssertException(typeof(T), exc);
AssertException(exc);
}

private static void AssertException(Type type, Exception exc)
private static void AssertException(Exception exc)
{
var b = MessagePackSerializer.Serialize(exc);
var des = MessagePackSerializer.Deserialize<Exception>(b);
Expand Down
4 changes: 4 additions & 0 deletions Lib9c/Action/ActivatedAccountsDoesNotExistsException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ public ActivatedAccountsDoesNotExistsException()
{
}

public ActivatedAccountsDoesNotExistsException(string message) : base(message)
{
}

public ActivatedAccountsDoesNotExistsException(
SerializationInfo info, StreamingContext context
) : base(info, context)
Expand Down
6 changes: 5 additions & 1 deletion Lib9c/Action/ActivationException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ public abstract class ActivationException : Exception
protected ActivationException()
{
}


protected ActivationException(string message) : base(message)
{
}

protected ActivationException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
Expand Down
Loading

0 comments on commit 8dda982

Please sign in to comment.