Browse Source

Use new configuration API for TIFF codec

pull/1570/head
Andrew Wilkinson 9 years ago
parent
commit
bace18e4ab
  1. 12
      src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs
  2. 18
      src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs
  3. 2
      src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
  4. 8
      src/ImageSharp/Formats/Tiff/ImageExtensions.cs
  5. 21
      src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs
  6. 12
      src/ImageSharp/Formats/Tiff/TiffDecoder.cs
  7. 22
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  8. 16
      src/ImageSharp/Formats/Tiff/TiffEncoder.cs
  9. 7
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  10. 40
      src/ImageSharp/Formats/Tiff/TiffEncoderOptions.cs
  11. 23
      src/ImageSharp/Formats/Tiff/TiffFormat.cs
  12. 36
      src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs
  13. 5
      src/ImageSharp/ImageFormats.cs
  14. 6
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderHeaderTests.cs
  15. 4
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderMetadataTests.cs
  16. 104
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffFormatTests.cs
  17. 89
      tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffImageFormatDetectorTests.cs
  18. 2
      tests/ImageSharp.Formats.Tiff.Tests/ImageSharp.Formats.Tiff.Tests.csproj

12
src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs

@ -5,6 +5,8 @@
namespace ImageSharp.Formats.Tiff
{
using System.Collections.Generic;
/// <summary>
/// Defines constants defined in the TIFF specification.
/// </summary>
@ -69,5 +71,15 @@ namespace ImageSharp.Formats.Tiff
/// Size (in bytes) of the Double data type
/// </summary>
public const int SizeOfDouble = 8;
/// <summary>
/// The list of mimetypes that equate to a tiff.
/// </summary>
public static readonly IEnumerable<string> MimeTypes = new[] { "image/tiff", "image/tiff-fx" };
/// <summary>
/// The list of file extensions that equate to a tiff.
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "tiff", "tif" };
}
}

18
src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs

@ -0,0 +1,18 @@
// <copyright file="ITiffDecoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
/// <summary>
/// Encapsulates the options for the <see cref="TiffDecoder"/>.
/// </summary>
public interface ITiffDecoderOptions
{
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
bool IgnoreMetadata { get; }
}
}

2
src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs

@ -8,7 +8,7 @@ namespace ImageSharp.Formats
/// <summary>
/// Encapsulates the options for the <see cref="TiffEncoder"/>.
/// </summary>
public interface ITiffEncoderOptions : IEncoderOptions
public interface ITiffEncoderOptions
{
}
}

8
src/ImageSharp/Formats/Tiff/ImageExtensions.cs

@ -36,16 +36,16 @@ namespace ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="options">The options for the encoder.</param>
/// <param name="encoder">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>
/// The <see cref="Image{TPixel}"/>.
/// </returns>
public static Image<TPixel> SaveAsTiff<TPixel>(this Image<TPixel> source, Stream stream, ITiffEncoderOptions options)
public static Image<TPixel> SaveAsTiff<TPixel>(this Image<TPixel> source, Stream stream, TiffEncoder encoder)
where TPixel : struct, IPixel<TPixel>
{
TiffEncoder encoder = new TiffEncoder();
encoder.Encode(source, stream, options);
encoder = encoder ?? new TiffEncoder();
encoder.Encode(source, stream);
return source;
}

21
src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs

@ -0,0 +1,21 @@
// <copyright file="TiffConfigurationModule.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
/// <summary>
/// Registers the image encoders, decoders and mime type detectors for the TIFF format.
/// </summary>
public sealed class TiffConfigurationModule : IConfigurationModule
{
/// <inheritdoc/>
public void Configure(Configuration host)
{
host.SetEncoder(ImageFormats.Tiff, new TiffEncoder());
host.SetDecoder(ImageFormats.Tiff, new TiffDecoder());
host.AddImageFormatDetector(new TiffImageFormatDetector());
}
}
}

12
src/ImageSharp/Formats/Tiff/TiffDecoder.cs

@ -6,20 +6,26 @@
namespace ImageSharp.Formats
{
using System.IO;
using ImageSharp.Formats.Tiff;
using ImageSharp.PixelFormats;
/// <summary>
/// Image decoder for generating an image out of a TIFF stream.
/// </summary>
public class TiffDecoder : IImageDecoder
public class TiffDecoder : IImageDecoder, ITiffDecoderOptions
{
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; set; }
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream, IDecoderOptions options)
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
Guard.NotNull(stream, "stream");
using (TiffDecoderCore decoder = new TiffDecoderCore(options, configuration))
using (TiffDecoderCore decoder = new TiffDecoderCore(configuration, this))
{
return decoder.Decode<TPixel>(stream);
}

22
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -18,24 +18,26 @@ namespace ImageSharp.Formats
internal class TiffDecoderCore : IDisposable
{
/// <summary>
/// The decoder options.
/// The global configuration
/// </summary>
private readonly IDecoderOptions options;
private readonly Configuration configuration;
/// <summary>
/// The global configuration
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
private readonly Configuration configuration;
private bool ignoreMetadata;
/// <summary>
/// Initializes a new instance of the <see cref="TiffDecoderCore" /> class.
/// </summary>
/// <param name="options">The decoder options.</param>
/// <param name="configuration">The configuration.</param>
public TiffDecoderCore(IDecoderOptions options, Configuration configuration)
/// <param name="options">The decoder options.</param>
public TiffDecoderCore(Configuration configuration, ITiffDecoderOptions options)
{
options = options ?? new TiffDecoder();
this.configuration = configuration ?? Configuration.Default;
this.options = options ?? new DecoderOptions();
this.ignoreMetadata = options.IgnoreMetadata;
}
/// <summary>
@ -45,8 +47,8 @@ namespace ImageSharp.Formats
/// <param name="isLittleEndian">A flag indicating if the file is encoded in little-endian or big-endian format.</param>
/// <param name="options">The decoder options.</param>
/// <param name="configuration">The configuration.</param>
public TiffDecoderCore(Stream stream, bool isLittleEndian, IDecoderOptions options, Configuration configuration)
: this(options, configuration)
public TiffDecoderCore(Stream stream, bool isLittleEndian, Configuration configuration, ITiffDecoderOptions options)
: this(configuration, options)
{
this.InputStream = stream;
this.IsLittleEndian = isLittleEndian;
@ -252,7 +254,7 @@ namespace ImageSharp.Formats
}
}
if (!this.options.IgnoreMetadata)
if (!this.ignoreMetadata)
{
if (ifd.TryGetIfdEntry(TiffTags.Artist, out TiffIfdEntry artistEntry))
{

16
src/ImageSharp/Formats/Tiff/TiffEncoder.cs

@ -12,28 +12,18 @@ namespace ImageSharp.Formats
/// <summary>
/// Encoder for writing the data image to a stream in TIFF format.
/// </summary>
public class TiffEncoder : IImageEncoder
public class TiffEncoder : IImageEncoder, ITiffEncoderOptions
{
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, IEncoderOptions options)
where TPixel : struct, IPixel<TPixel>
{
ITiffEncoderOptions tiffOptions = TiffEncoderOptions.Create(options);
this.Encode(image, stream, tiffOptions);
}
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="options">The options for the encoder.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream, ITiffEncoderOptions options)
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
var encode = new TiffEncoderCore(options);
var encode = new TiffEncoderCore(this);
encode.Encode(image, stream);
}
}

7
src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs

@ -25,18 +25,13 @@ namespace ImageSharp.Formats
/// </summary>
internal sealed class TiffEncoderCore
{
/// <summary>
/// The options for the encoder.
/// </summary>
private readonly ITiffEncoderOptions options;
/// <summary>
/// Initializes a new instance of the <see cref="TiffEncoderCore"/> class.
/// </summary>
/// <param name="options">The options for the encoder.</param>
public TiffEncoderCore(ITiffEncoderOptions options)
{
this.options = options ?? new TiffEncoderOptions();
options = options ?? new TiffEncoder();
}
/// <summary>

40
src/ImageSharp/Formats/Tiff/TiffEncoderOptions.cs

@ -1,40 +0,0 @@
// <copyright file="TiffEncoderOptions.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
/// <summary>
/// Encapsulates the options for the <see cref="TiffEncoder"/>.
/// </summary>
public sealed class TiffEncoderOptions : EncoderOptions, ITiffEncoderOptions
{
/// <summary>
/// Initializes a new instance of the <see cref="TiffEncoderOptions"/> class.
/// </summary>
public TiffEncoderOptions()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TiffEncoderOptions"/> class.
/// </summary>
/// <param name="options">The options for the encoder.</param>
private TiffEncoderOptions(IEncoderOptions options)
: base(options)
{
}
/// <summary>
/// Converts the options to a <see cref="ITiffEncoderOptions"/> instance with a
/// cast or by creating a new instance with the specfied options.
/// </summary>
/// <param name="options">The options for the encoder.</param>
/// <returns>The options for the <see cref="TiffEncoder"/>.</returns>
internal static ITiffEncoderOptions Create(IEncoderOptions options)
{
return options as ITiffEncoderOptions ?? new TiffEncoderOptions(options);
}
}
}

23
src/ImageSharp/Formats/Tiff/TiffFormat.cs

@ -6,6 +6,7 @@
namespace ImageSharp.Formats
{
using System.Collections.Generic;
using ImageSharp.Formats.Tiff;
/// <summary>
/// Encapsulates the means to encode and decode Tiff images.
@ -13,29 +14,15 @@ namespace ImageSharp.Formats
public class TiffFormat : IImageFormat
{
/// <inheritdoc/>
public string MimeType => "image/tiff";
public string Name => "TIFF";
/// <inheritdoc/>
public string Extension => "tif";
public string DefaultMimeType => "image/tiff";
/// <inheritdoc/>
public IEnumerable<string> SupportedExtensions => new string[] { "tif", "tiff" };
public IEnumerable<string> MimeTypes => TiffConstants.MimeTypes;
/// <inheritdoc/>
public IImageDecoder Decoder => new TiffDecoder();
/// <inheritdoc/>
public IImageEncoder Encoder => new TiffEncoder();
/// <inheritdoc/>
public int HeaderSize => 4;
/// <inheritdoc/>
public bool IsSupportedFileFormat(byte[] header)
{
return header.Length >= this.HeaderSize &&
((header[0] == 0x49 && header[1] == 0x49 && header[2] == 0x2A && header[3] == 0x00) || // Little-endian
(header[0] == 0x4D && header[1] == 0x4D && header[2] == 0x00 && header[3] == 0x2A)); // Big-endian
}
public IEnumerable<string> FileExtensions => TiffConstants.FileExtensions;
}
}

36
src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs

@ -0,0 +1,36 @@
// <copyright file="TiffImageFormatDetector.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Formats
{
using System;
/// <summary>
/// Detects tiff file headers
/// </summary>
public sealed class TiffImageFormatDetector : IImageFormatDetector
{
/// <inheritdoc/>
public int HeaderSize => 4;
/// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header)
{
if (this.IsSupportedFileFormat(header))
{
return ImageFormats.Tiff;
}
return null;
}
private bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
{
return header.Length >= this.HeaderSize &&
((header[0] == 0x49 && header[1] == 0x49 && header[2] == 0x2A && header[3] == 0x00) || // Little-endian
(header[0] == 0x4D && header[1] == 0x4D && header[2] == 0x00 && header[3] == 0x2A)); // Big-endian
}
}
}

5
src/ImageSharp/ImageFormats.cs

@ -31,5 +31,10 @@ namespace ImageSharp
/// The format details for the bitmaps.
/// </summary>
public static readonly IImageFormat Bitmap = new BmpFormat();
/// <summary>
/// The format details for the tiffs.
/// </summary>
public static readonly IImageFormat Tiff = new TiffFormat();
}
}

6
tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderHeaderTests.cs

@ -68,7 +68,7 @@ namespace ImageSharp.Tests
TiffDecoder decoder = new TiffDecoder();
ImageFormatException e = Assert.Throws<ImageFormatException>(() => { decoder.Decode<Rgba32>(Configuration.Default, stream, null); });
ImageFormatException e = Assert.Throws<ImageFormatException>(() => { decoder.Decode<Rgba32>(Configuration.Default, stream); });
Assert.Equal("Invalid TIFF file header.", e.Message);
}
@ -86,7 +86,7 @@ namespace ImageSharp.Tests
TiffDecoder decoder = new TiffDecoder();
ImageFormatException e = Assert.Throws<ImageFormatException>(() => { decoder.Decode<Rgba32>(Configuration.Default, stream, null); });
ImageFormatException e = Assert.Throws<ImageFormatException>(() => { decoder.Decode<Rgba32>(Configuration.Default, stream); });
Assert.Equal("Invalid TIFF file header.", e.Message);
}
@ -103,7 +103,7 @@ namespace ImageSharp.Tests
TiffDecoder decoder = new TiffDecoder();
ImageFormatException e = Assert.Throws<ImageFormatException>(() => { decoder.Decode<Rgba32>(Configuration.Default, stream, null); });
ImageFormatException e = Assert.Throws<ImageFormatException>(() => { decoder.Decode<Rgba32>(Configuration.Default, stream); });
Assert.Equal("Invalid TIFF file header.", e.Message);
}

4
tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderMetadataTests.cs

@ -124,8 +124,8 @@ namespace ImageSharp.Tests
}
.ToStream(isLittleEndian);
DecoderOptions options = new DecoderOptions() { IgnoreMetadata = true };
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, options, null);
TiffDecoder options = new TiffDecoder() { IgnoreMetadata = true };
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, options);
TiffIfd ifd = decoder.ReadIfd(0);
Image<Rgba32> image = new Image<Rgba32>(null, 20, 20);

104
tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffFormatTests.cs

@ -12,110 +12,16 @@ namespace ImageSharp.Tests
public class TiffFormatTests
{
public static object[][] IsLittleEndianValues = new[] { new object[] { false },
new object[] { true } };
[Fact]
public void FormatProperties_AreAsExpected()
{
TiffFormat tiffFormat = new TiffFormat();
Assert.Equal("image/tiff", tiffFormat.MimeType);
Assert.Equal("tif", tiffFormat.Extension);
Assert.Contains("tif", tiffFormat.SupportedExtensions);
Assert.Contains("tiff", tiffFormat.SupportedExtensions);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void IsSupportedFileFormat_ReturnsTrue_ForValidFile(bool isLittleEndian)
{
byte[] bytes = new TiffGenHeader()
{
FirstIfd = new TiffGenIfd()
}
.ToBytes(isLittleEndian);
TiffFormat tiffFormat = new TiffFormat();
byte[] headerBytes = bytes.Take(tiffFormat.HeaderSize).ToArray();
bool isSupported = tiffFormat.IsSupportedFileFormat(headerBytes);
Assert.True(isSupported);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void IsSupportedFileFormat_ReturnsFalse_WithInvalidByteOrderMarkers(bool isLittleEndian)
{
byte[] bytes = new TiffGenHeader()
{
FirstIfd = new TiffGenIfd(),
ByteOrderMarker = 0x1234
}
.ToBytes(isLittleEndian);
TiffFormat tiffFormat = new TiffFormat();
byte[] headerBytes = bytes.Take(tiffFormat.HeaderSize).ToArray();
bool isSupported = tiffFormat.IsSupportedFileFormat(headerBytes);
Assert.False(isSupported);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void IsSupportedFileFormat_ReturnsFalse_WithIncorrectMagicNumber(bool isLittleEndian)
{
byte[] bytes = new TiffGenHeader()
{
FirstIfd = new TiffGenIfd(),
MagicNumber = 32
}
.ToBytes(isLittleEndian);
TiffFormat tiffFormat = new TiffFormat();
byte[] headerBytes = bytes.Take(tiffFormat.HeaderSize).ToArray();
bool isSupported = tiffFormat.IsSupportedFileFormat(headerBytes);
Assert.False(isSupported);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void IsSupportedFileFormat_ReturnsFalse_WithShortHeader(bool isLittleEndian)
{
byte[] bytes = new TiffGenHeader()
{
FirstIfd = new TiffGenIfd()
}
.ToBytes(isLittleEndian);
TiffFormat tiffFormat = new TiffFormat();
byte[] headerBytes = bytes.Take(tiffFormat.HeaderSize - 1).ToArray();
bool isSupported = tiffFormat.IsSupportedFileFormat(headerBytes);
Assert.False(isSupported);
}
[Fact]
public void Decoder_ReturnsTiffDecoder()
{
TiffFormat tiffFormat = new TiffFormat();
var decoder = tiffFormat.Decoder;
Assert.NotNull(decoder);
Assert.IsType<TiffDecoder>(decoder);
}
[Fact]
public void Encoder_ReturnsTiffEncoder()
{
TiffFormat tiffFormat = new TiffFormat();
var encoder = tiffFormat.Encoder;
Assert.NotNull(encoder);
Assert.IsType<TiffEncoder>(encoder);
Assert.Equal("TIFF", tiffFormat.Name);
Assert.Equal("image/tiff", tiffFormat.DefaultMimeType);
Assert.Contains("image/tiff", tiffFormat.MimeTypes);
Assert.Contains("tif", tiffFormat.FileExtensions);
Assert.Contains("tiff", tiffFormat.FileExtensions);
}
}
}

89
tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffImageFormatDetectorTests.cs

@ -0,0 +1,89 @@
// <copyright file="TiffImageFormatDetectorTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests
{
using System.Linq;
using Xunit;
using ImageSharp.Formats;
public class TiffImageFormatDetectorTests
{
public static object[][] IsLittleEndianValues = new[] { new object[] { false },
new object[] { true } };
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void DetectFormat_ReturnsTiffFormat_ForValidFile(bool isLittleEndian)
{
byte[] bytes = new TiffGenHeader()
{
FirstIfd = new TiffGenIfd()
}
.ToBytes(isLittleEndian);
TiffImageFormatDetector formatDetector = new TiffImageFormatDetector();
byte[] headerBytes = bytes.Take(formatDetector.HeaderSize).ToArray();
var format = formatDetector.DetectFormat(headerBytes);
Assert.NotNull(format);
Assert.IsType<TiffFormat>(format);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void DetectFormat_ReturnsNull_WithInvalidByteOrderMarkers(bool isLittleEndian)
{
byte[] bytes = new TiffGenHeader()
{
FirstIfd = new TiffGenIfd(),
ByteOrderMarker = 0x1234
}
.ToBytes(isLittleEndian);
TiffImageFormatDetector formatDetector = new TiffImageFormatDetector();
byte[] headerBytes = bytes.Take(formatDetector.HeaderSize).ToArray();
var format = formatDetector.DetectFormat(headerBytes);
Assert.Null(format);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void DetectFormat_ReturnsNull_WithIncorrectMagicNumber(bool isLittleEndian)
{
byte[] bytes = new TiffGenHeader()
{
FirstIfd = new TiffGenIfd(),
MagicNumber = 32
}
.ToBytes(isLittleEndian);
TiffImageFormatDetector formatDetector = new TiffImageFormatDetector();
byte[] headerBytes = bytes.Take(formatDetector.HeaderSize).ToArray();
var format = formatDetector.DetectFormat(headerBytes);
Assert.Null(format);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void DetectFormat_ReturnsNull_WithShortHeader(bool isLittleEndian)
{
byte[] bytes = new TiffGenHeader()
{
FirstIfd = new TiffGenIfd()
}
.ToBytes(isLittleEndian);
TiffImageFormatDetector formatDetector = new TiffImageFormatDetector();
byte[] headerBytes = bytes.Take(formatDetector.HeaderSize - 1).ToArray();
var format = formatDetector.DetectFormat(headerBytes);
Assert.Null(format);
}
}
}

2
tests/ImageSharp.Formats.Tiff.Tests/ImageSharp.Formats.Tiff.Tests.csproj

@ -7,7 +7,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="System.ValueTuple" Version="4.3.0" />
<PackageReference Include="System.ValueTuple" Version="4.4.0-preview1-25305-02" />
<PackageReference Include="xunit" Version="2.3.0-beta2-build3683" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.0-beta2-build1317" />
</ItemGroup>

Loading…
Cancel
Save