Tests for path/hash serialization

This commit is contained in:
Timothy Baldridge 2020-03-26 06:28:03 -06:00
parent a7e220b779
commit 31c808deea
6 changed files with 385 additions and 76 deletions

View File

@ -0,0 +1,73 @@
using System.IO;
using System.Threading.Tasks;
using Xunit;
namespace Wabbajack.Common.Test
{
public class SerializationTests
{
[Fact]
public async Task HashRoundTrips()
{
await RoundTrips(Hash.FromULong(42));
await RoundTrips(Hash.FromULong(ulong.MaxValue));
await RoundTrips(Hash.FromULong(ulong.MinValue));
await RoundTrips(Hash.FromLong(long.MaxValue));
await RoundTrips(Hash.FromLong(long.MinValue));
}
[Fact]
public async Task RelativePathsRoundTrips()
{
await RoundTrips((RelativePath)@"foo.txt");
await RoundTrips((RelativePath)@"foo");
await RoundTrips((RelativePath)@"\\far\\foo.txt");
await RoundTrips((RelativePath)@"\\foo");
await RoundTrips((RelativePath)@"\\baz");
}
[Fact]
public async Task AbsolutePathRoundTrips()
{
await RoundTrips((AbsolutePath)@"c:\foo.txt");
await RoundTrips((AbsolutePath)@"c:\foo");
await RoundTrips((AbsolutePath)@"z:\far\foo.txt");
await RoundTrips((AbsolutePath)@"r:\foo");
await RoundTrips((AbsolutePath)@"f:\baz");
}
[Fact]
public async Task HashRelativePathRoundTrips()
{
await RoundTrips(new HashRelativePath(Hash.FromULong(42), (RelativePath)"foo/bar.zip", (RelativePath)"baz.txt"));
await RoundTrips(new HashRelativePath(Hash.FromULong(42)));
}
[Fact]
public async Task FullPathRoundTrips()
{
await RoundTrips(new FullPath((AbsolutePath)@"c:\tmp", (RelativePath)"foo/bar.zip", (RelativePath)"baz.txt"));
await RoundTrips(new FullPath((AbsolutePath)@"c:\"));
}
private static async Task RoundTrips<T>(T input)
{
Assert.Equal(input, RoundTripJson(input));
Assert.Equal(input, await RoundTripMessagePack(input));
}
private static T RoundTripJson<T>(T input)
{
return input.ToJSON().FromJSONString<T>();
}
private static async Task<T> RoundTripMessagePack<T>(T input)
{
await using var ms = new MemoryStream();
await ms.WriteAsMessagePackAsync(input);
ms.Position = 0;
return await ms.ReadAsMessagePackAsync<T>();
}
}
}

View File

@ -81,6 +81,11 @@ namespace Wabbajack.Common
{
return new Hash(BitConverter.ToUInt64(BitConverter.GetBytes(argHash)));
}
public static Hash FromULong(in ulong argHash)
{
return new Hash(argHash);
}
public static Hash FromHex(string xxHashAsHex)
{

197
Wabbajack.Common/Json.cs Normal file
View File

@ -0,0 +1,197 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Newtonsoft.Json;
using File = Alphaleonis.Win32.Filesystem.File;
namespace Wabbajack.Common
{
public static partial class Utils
{
public static List<JsonConverter> Converters = new List<JsonConverter>
{
new HashJsonConverter(),
new RelativePathConverter(),
new AbolutePathConverter(),
new HashRelativePathConverter(),
new FullPathConverter()
};
public static void ToJSON<T>(this T obj, string filename)
{
if (File.Exists(filename))
File.Delete(filename);
File.WriteAllText(filename,
JsonConvert.SerializeObject(obj, Formatting.Indented,
new JsonSerializerSettings {
TypeNameHandling = TypeNameHandling.Auto,
Converters = Converters}));
}
public static string ToJSON<T>(this T obj,
TypeNameHandling handling = TypeNameHandling.All,
TypeNameAssemblyFormatHandling format = TypeNameAssemblyFormatHandling.Full,
bool prettyPrint = false)
{
return JsonConvert.SerializeObject(obj, Formatting.Indented,
new JsonSerializerSettings {TypeNameHandling = handling,
TypeNameAssemblyFormatHandling = format,
Formatting = prettyPrint ? Formatting.Indented : Formatting.None,
Converters = Converters
});
}
public static T FromJSON<T>(this string filename,
TypeNameHandling handling = TypeNameHandling.All,
TypeNameAssemblyFormatHandling format = TypeNameAssemblyFormatHandling.Full)
{
return JsonConvert.DeserializeObject<T>(File.ReadAllText(filename),
new JsonSerializerSettings {TypeNameHandling = handling,
TypeNameAssemblyFormatHandling = format,
Converters = Converters
});
}
public static T FromJSONString<T>(this string data,
TypeNameHandling handling = TypeNameHandling.All,
TypeNameAssemblyFormatHandling format = TypeNameAssemblyFormatHandling.Full)
{
return JsonConvert.DeserializeObject<T>(data,
new JsonSerializerSettings {TypeNameHandling = handling,
TypeNameAssemblyFormatHandling = format,
Converters = Converters
});
}
public static T FromJSON<T>(this Stream data)
{
var s = Encoding.UTF8.GetString(data.ReadAll());
try
{
return JsonConvert.DeserializeObject<T>(s,
new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
Converters = Converters
});
}
catch (JsonSerializationException)
{
var error = JsonConvert.DeserializeObject<NexusErrorResponse>(s,
new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto});
if (error != null)
Log($"Exception while deserializing\nError code: {error.code}\nError message: {error.message}");
throw;
}
}
private class HashJsonConverter : JsonConverter<Hash>
{
public override void WriteJson(JsonWriter writer, Hash value, JsonSerializer serializer)
{
writer.WriteValue(value.ToBase64());
}
public override Hash ReadJson(JsonReader reader, Type objectType, Hash existingValue, bool hasExistingValue, JsonSerializer serializer)
{
return Hash.FromBase64((string)reader.Value);
}
}
private class RelativePathConverter : JsonConverter<RelativePath>
{
public override void WriteJson(JsonWriter writer, RelativePath value, JsonSerializer serializer)
{
writer.WriteValue((string)value);
}
public override RelativePath ReadJson(JsonReader reader, Type objectType, RelativePath existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
return (RelativePath)(string)reader.Value;
}
}
private class AbolutePathConverter : JsonConverter<AbsolutePath>
{
public override void WriteJson(JsonWriter writer, AbsolutePath value, JsonSerializer serializer)
{
writer.WriteValue((string)value);
}
public override AbsolutePath ReadJson(JsonReader reader, Type objectType, AbsolutePath existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
return (AbsolutePath)(string)reader.Value;
}
}
private class HashRelativePathConverter : JsonConverter<HashRelativePath>
{
public override void WriteJson(JsonWriter writer, HashRelativePath value, JsonSerializer serializer)
{
writer.WriteStartArray();
writer.WriteValue(value.BaseHash.ToBase64());
foreach (var itm in value.Paths)
writer.WriteValue((string)itm);
writer.WriteEndArray();
}
public override HashRelativePath ReadJson(JsonReader reader, Type objectType, HashRelativePath existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.StartArray)
throw new JsonException("Invalid JSON state while reading Hash Relative Path");
reader.Read();
var hash = Hash.FromBase64((string)reader.Value);
var paths = new List<RelativePath>();
reader.Read();
while (reader.TokenType != JsonToken.EndArray)
{
paths.Add((RelativePath)(string)reader.Value);
reader.Read();
}
return new HashRelativePath(hash, paths.ToArray());
}
}
private class FullPathConverter : JsonConverter<FullPath>
{
public override void WriteJson(JsonWriter writer, FullPath value, JsonSerializer serializer)
{
writer.WriteStartArray();
writer.WriteValue((string)value.Base);
foreach (var itm in value.Paths)
writer.WriteValue((string)itm);
writer.WriteEndArray();
}
public override FullPath ReadJson(JsonReader reader, Type objectType, FullPath existingValue, bool hasExistingValue,
JsonSerializer serializer)
{
if (reader.TokenType != JsonToken.StartArray)
throw new JsonException("Invalid JSON state while reading Hash Relative Path");
reader.Read();
var abs = (AbsolutePath)(string)reader.Value;
var paths = new List<RelativePath>();
reader.Read();
while (reader.TokenType != JsonToken.EndArray)
{
paths.Add((RelativePath)(string)reader.Value);
reader.Read();
}
return new FullPath(abs, paths.ToArray());
}
}
}
}

View File

@ -1,5 +1,7 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using MessagePack;
using MessagePack.Formatters;
@ -15,7 +17,14 @@ namespace Wabbajack.Common
private static void MessagePackInit()
{
_resolver = CompositeResolver.Create(
new List<IMessagePackFormatter>{new HashFormatter()},
new List<IMessagePackFormatter>
{
new HashFormatter(),
new RelativePathFormatter(),
new AbsolutePathFormatter(),
new HashRelativePathFormatter(),
new FullPathFormatter()
},
new List<IFormatterResolver> {StandardResolver.Instance}
);
_messagePackOptions = MessagePackSerializerOptions.Standard
@ -87,5 +96,85 @@ namespace Wabbajack.Common
}
}
public class RelativePathFormatter : IMessagePackFormatter<RelativePath>
{
public void Serialize(ref MessagePackWriter writer, RelativePath value, MessagePackSerializerOptions options)
{
var encoded = Encoding.UTF8.GetBytes((string)value);
writer.WriteString(encoded);
}
public RelativePath Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
return (RelativePath)reader.ReadString();
}
}
public class AbsolutePathFormatter : IMessagePackFormatter<AbsolutePath>
{
public void Serialize(ref MessagePackWriter writer, AbsolutePath value, MessagePackSerializerOptions options)
{
var encoded = Encoding.UTF8.GetBytes((string)value);
writer.WriteString(encoded);
}
public AbsolutePath Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
return (AbsolutePath)reader.ReadString();
}
}
public class HashRelativePathFormatter : IMessagePackFormatter<HashRelativePath>
{
public void Serialize(ref MessagePackWriter writer, HashRelativePath value, MessagePackSerializerOptions options)
{
writer.WriteArrayHeader(value.Paths.Length + 1);
writer.WriteUInt64((ulong)value.BaseHash);
foreach (var path in value.Paths)
{
var encoded = Encoding.UTF8.GetBytes((string)path);
writer.WriteString(encoded);
}
}
public HashRelativePath Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
var header = reader.ReadArrayHeader();
var hash = Hash.FromULong(reader.ReadUInt64());
var paths = new RelativePath[header - 1];
for (int idx = 0; idx < header - 1; idx += 1)
{
paths[idx] = (RelativePath)reader.ReadString();
}
return new HashRelativePath(hash, paths);
}
}
public class FullPathFormatter : IMessagePackFormatter<FullPath>
{
public void Serialize(ref MessagePackWriter writer, FullPath value, MessagePackSerializerOptions options)
{
writer.WriteArrayHeader(value.Paths.Length + 1);
writer.WriteString(Encoding.UTF8.GetBytes((string)value.Base));
foreach (var path in value.Paths)
{
var encoded = Encoding.UTF8.GetBytes((string)path);
writer.WriteString(encoded);
}
}
public FullPath Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
var header = reader.ReadArrayHeader();
var basePath = (AbsolutePath)reader.ReadString();
var paths = new RelativePath[header - 1];
for (int idx = 0; idx < header - 1; idx += 1)
{
paths[idx] = (RelativePath)reader.ReadString();
}
return new FullPath(basePath, paths);
}
}
#endregion
}

View File

@ -605,7 +605,7 @@ namespace Wabbajack.Common
}
}
public struct HashRelativePath
public struct HashRelativePath : IEquatable<HashRelativePath>
{
private static RelativePath[] EMPTY_PATH;
public Hash BaseHash { get; }
@ -622,14 +622,13 @@ namespace Wabbajack.Common
Paths = paths;
}
public string ToString()
public override string ToString()
{
return string.Join("|", Paths.Select(t => t.ToString()).Cons(BaseHash.ToString()));
}
public static bool operator ==(HashRelativePath a, HashRelativePath b)
{
if (a.BaseHash != b.BaseHash || a.Paths.Length == b.Paths.Length)
if (a.BaseHash != b.BaseHash || a.Paths.Length != b.Paths.Length)
{
return false;
}
@ -649,6 +648,21 @@ namespace Wabbajack.Common
{
return !(a == b);
}
public bool Equals(HashRelativePath other)
{
return this == other;
}
public override bool Equals(object obj)
{
return obj is HashRelativePath other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(BaseHash, Paths);
}
}
public struct FullPath : IEquatable<FullPath>, IPath
@ -658,7 +672,7 @@ namespace Wabbajack.Common
private readonly int _hash;
public FullPath(AbsolutePath basePath, RelativePath[] paths)
public FullPath(AbsolutePath basePath, params RelativePath[] paths)
{
Base = basePath;
Paths = paths;

View File

@ -337,75 +337,6 @@ namespace Wabbajack.Common
return new DynamicIniData(new FileIniDataParser().ReadData(new StreamReader(new MemoryStream(Encoding.UTF8.GetBytes(file)))));
}
public static void ToJSON<T>(this T obj, string filename)
{
if (File.Exists(filename))
File.Delete(filename);
File.WriteAllText(filename,
JsonConvert.SerializeObject(obj, Formatting.Indented,
new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto}));
}
public static ulong ToMilliseconds(this DateTime date)
{
return (ulong) (date - new DateTime(1970, 1, 1)).TotalMilliseconds;
}
public static string ToJSON<T>(this T obj,
TypeNameHandling handling = TypeNameHandling.All,
TypeNameAssemblyFormatHandling format = TypeNameAssemblyFormatHandling.Full,
bool prettyPrint = false)
{
return JsonConvert.SerializeObject(obj, Formatting.Indented,
new JsonSerializerSettings {TypeNameHandling = handling,
TypeNameAssemblyFormatHandling = format,
Formatting = prettyPrint ? Formatting.Indented : Formatting.None});
}
public static T FromJSON<T>(this string filename,
TypeNameHandling handling = TypeNameHandling.All,
TypeNameAssemblyFormatHandling format = TypeNameAssemblyFormatHandling.Full)
{
return JsonConvert.DeserializeObject<T>(File.ReadAllText(filename),
new JsonSerializerSettings {TypeNameHandling = handling, TypeNameAssemblyFormatHandling = format});
}
/*
public static T FromBSON<T>(this string filename, bool root_is_array = false)
{
using (var fo = File.OpenRead(filename))
using (var br = new BsonDataReader(fo, root_is_array, DateTimeKind.Local))
{
var serializer = JsonSerializer.Create(new JsonSerializerSettings
{TypeNameHandling = TypeNameHandling.Auto});
return serializer.Deserialize<T>(br);
}
}*/
public static T FromJSONString<T>(this string data,
TypeNameHandling handling = TypeNameHandling.All,
TypeNameAssemblyFormatHandling format = TypeNameAssemblyFormatHandling.Full)
{
return JsonConvert.DeserializeObject<T>(data,
new JsonSerializerSettings {TypeNameHandling = handling, TypeNameAssemblyFormatHandling = format});
}
public static T FromJSON<T>(this Stream data)
{
var s = Encoding.UTF8.GetString(data.ReadAll());
try
{
return JsonConvert.DeserializeObject<T>(s,
new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto});
}
catch (JsonSerializationException)
{
var error = JsonConvert.DeserializeObject<NexusErrorResponse>(s,
new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto});
if (error != null)
Log($"Exception while deserializing\nError code: {error.code}\nError message: {error.message}");
throw;
}
}
public static bool FileExists(this string filename)
{