Skip to content

Commit

Permalink
Tests: Lint Configuration json files
Browse files Browse the repository at this point in the history
This works by deserializing the json files and then re-serializing them
again afterwards with the appropriate settings. This also applies some
code changes to accomodate correct parsing of the MaxX/MaxY values which
does not make sense to have a trailing decimal on.
  • Loading branch information
gonX committed Nov 26, 2023
1 parent 5218f43 commit ba1d056
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 0 deletions.
82 changes: 82 additions & 0 deletions OpenTabletDriver.Tests/ConfigurationTest.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;
using Newtonsoft.Json.Schema.Generation;
Expand Down Expand Up @@ -300,6 +303,85 @@ public void Configurations_Verify_Configs_With_Schema()
Assert.False(failed);
}

/// <summary>
/// Ensures that configuration formatting/linting matches expectations, which are:
/// - 2 space indentation
/// - Newline at end of file
/// - Consistent newline format
/// </summary>
[Fact]
public void Configurations_Are_Linted()
{
const int maxLinesToOutput = 3;

var serializer = new JsonSerializer();
var failedFiles = 0;

var ourJsonSb = new StringBuilder();
using var strw = new StringWriter(ourJsonSb);
using var jtw = new JsonTextWriter(strw);
jtw.Formatting = Formatting.Indented;
jtw.Indentation = 2;

foreach (var (tabletFilename, theirJson) in ConfigFiles)
{
ourJsonSb.Clear();
var ourJsonObj = JsonConvert.DeserializeObject<TabletConfiguration>(theirJson);

serializer.Serialize(jtw, ourJsonObj);
ourJsonSb.AppendLine(); // otherwise we won't have an EOL at EOF

var ourJson = ourJsonSb.ToString();

var failedLines = DoesJsonMatch(ourJson, theirJson);

if (failedLines.Any() || !string.Equals(theirJson, ourJson)) // second check ensures EOL markers are equivalent
{
failedFiles++;
_testOutputHelper.WriteLine(
$"- Tablet Configuration '{tabletFilename}' lint check failed with the following errors:");

foreach (var (line, error) in failedLines.Take(maxLinesToOutput))
_testOutputHelper.WriteLine($" Line {line}: {error}");
if (failedLines.Count > maxLinesToOutput)
_testOutputHelper.WriteLine($" Truncated an additional {failedLines.Count - maxLinesToOutput} mismatching lines - wrong indent?");
else if (failedLines.Count == 0)
_testOutputHelper.WriteLine(" Generic mismatch (line endings?)");
}
}

Assert.Equal(0, failedFiles);
}

private static IList<(int, string)> DoesJsonMatch(string ourJson, string theirJson)
{
int line = 0;
var rv = new List<(int, string)>();

using var ourSr = new StringReader(ourJson);
using var theirSr = new StringReader(theirJson);
while (true)
{
var ourLine = ourSr.ReadLine();
var theirLine = theirSr.ReadLine();
line++;

if (ourLine == null && theirLine == null)
break; // success for file

var ourLineOutput = ourLine ?? "EOF";
var theirLineOutput = theirLine ?? "EOF";

if (ourLine == null || theirLine == null || !string.Equals(ourLine, theirLine))
rv.Add((line, $"Expected '{ourLineOutput}' got '{theirLineOutput}'"));

if (ourLine == null || theirLine == null)
break;
}

return rv;
}

private static void DisallowAdditionalItemsAndProperties(JSchema schema)
{
schema.AllowAdditionalItems = false;
Expand Down
42 changes: 42 additions & 0 deletions OpenTabletDriver/Tablet/DigitizerSpecifications.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Newtonsoft.Json;

namespace OpenTabletDriver.Tablet
{
Expand All @@ -14,26 +17,65 @@ public class DigitizerSpecifications
/// The width of the digitizer in millimeters.
/// </summary>
[Required(ErrorMessage = $"Digitizer ${nameof(Width)} must be defined")]
[JsonConverter(typeof(DecimalJsonConverter))]
public float Width { set; get; }

/// <summary>
/// The height of the digitizer in millimeters.
/// </summary>
[Required(ErrorMessage = $"Digitizer ${nameof(Height)} must be defined")]
[JsonConverter(typeof(DecimalJsonConverter))]
public float Height { set; get; }

/// <summary>
/// The maximum X coordinate for the digitizer.
/// </summary>
[Required(ErrorMessage = $"Digitizer ${nameof(MaxX)} must be defined")]
[JsonConverter(typeof(DecimalJsonConverter))]
[DisplayName("Max X")]
public float MaxX { set; get; }

/// <summary>
/// The maximum Y coordinate for the digitizer.
/// </summary>
[Required(ErrorMessage = $"Digitizer ${nameof(MaxY)} must be defined")]
[JsonConverter(typeof(DecimalJsonConverter))]
[DisplayName("Max Y")]
public float MaxY { set; get; }
}

public class DecimalJsonConverter : JsonConverter
{
public override bool CanRead => false;
public override bool CanWrite => true;

public override bool CanConvert(Type objectType)
{
return objectType == typeof(double);
}

public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
if (value == null)
throw new ArgumentNullException();

writer.WriteRawValue(IsWholeValue(value)
? JsonConvert.ToString(Convert.ToInt64(value))
: JsonConvert.ToString(value));
}

public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}

[SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
private static bool IsWholeValue(object value)
{
if (value is float floatValue)
return floatValue == Math.Truncate(floatValue);

return false;
}
}
}

0 comments on commit ba1d056

Please sign in to comment.