Skip to content

Commit

Permalink
Implement CsvDataSourceAttribute
Browse files Browse the repository at this point in the history
  • Loading branch information
Youssef1313 committed Nov 21, 2024
1 parent 153e889 commit ccb4a69
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<PropertyGroup Label="Product dependencies">
<SystemDataOleDbVersion>8.0.0</SystemDataOleDbVersion>
<AspireHostingTestingVersion>8.2.2</AspireHostingTestingVersion>
<MicrosoftBuildVersion>17.11.4</MicrosoftBuildVersion>
<MicrosoftCodeAnalysisAnalyzersVersion>3.11.0-beta1.24508.2</MicrosoftCodeAnalysisAnalyzersVersion>
Expand Down Expand Up @@ -31,6 +32,7 @@
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
</ItemGroup>
<ItemGroup Label="Product dependencies">
<PackageVersion Include="System.Data.OleDb" Version="$(SystemDataOleDbVersion)" />
<PackageVersion Include="Microsoft.ApplicationInsights" Version="2.22.0" />
<PackageVersion Include="Microsoft.Build.Framework" Version="$(MicrosoftBuildVersion)" />
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="$(MicrosoftBuildVersion)" />
Expand Down
7 changes: 7 additions & 0 deletions TestFx.sln
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Testing.Platform.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Testing.Extensions.MSBuild", "src\Platform\Microsoft.Testing.Extensions.MSBuild\Microsoft.Testing.Extensions.MSBuild.csproj", "{8CE782A2-7374-4916-9C69-1F87E51A64A9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestFramework.Extensions.Csv", "src\TestFramework\TestFramework.Extensions.Csv\TestFramework.Extensions.Csv.csproj", "{536A4485-F77F-4617-8449-2DCB387514AB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -483,6 +485,10 @@ Global
{8CE782A2-7374-4916-9C69-1F87E51A64A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8CE782A2-7374-4916-9C69-1F87E51A64A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8CE782A2-7374-4916-9C69-1F87E51A64A9}.Release|Any CPU.Build.0 = Release|Any CPU
{536A4485-F77F-4617-8449-2DCB387514AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{536A4485-F77F-4617-8449-2DCB387514AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{536A4485-F77F-4617-8449-2DCB387514AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{536A4485-F77F-4617-8449-2DCB387514AB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -567,6 +573,7 @@ Global
{573C617F-6BB2-403A-AD87-E00A7FD537F0} = {BB874DF1-44FE-415A-B634-A6B829107890}
{F422398C-72CD-43EA-AC8E-E0DBD08E5563} = {BB874DF1-44FE-415A-B634-A6B829107890}
{8CE782A2-7374-4916-9C69-1F87E51A64A9} = {6AEE1440-FDF0-4729-8196-B24D0E333550}
{536A4485-F77F-4617-8449-2DCB387514AB} = {E48AC786-E150-4F41-9A16-32F02E4493D8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {31E0F4D5-975A-41CC-933E-545B2201FAF9}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Data;
using System.Data.OleDb;
using System.Globalization;
using System.Reflection;

namespace Microsoft.VisualStudio.TestTools.UnitTesting;

/// <summary>
/// Attribute to define dynamic data from a CSV file for a test method.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class CsvDataSourceAttribute : Attribute, ITestDataSource
{
// Template used to map from a filename to a DB connection string
private const string CsvConnectionTemplate = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source={0};Persist Security Info=False;Extended Properties=\"text;HDR=YES;FMT=Delimited\"";
private const string CsvConnectionTemplate64 = "Provider=Microsoft.Ace.OLEDB.12.0;Data Source={0};Persist Security Info=False;Extended Properties=\"text;HDR=YES;FMT=Delimited\"";

public CsvDataSourceAttribute(string fileName)
=> FileName = fileName;

internal string FileName { get; }

IEnumerable<object?[]> ITestDataSource.GetData(MethodInfo methodInfo)
{
// We specifically use OleDb to read a CSV file...
// We better work with a full path, if nothing else, errors become easier to report
string fullPath = Path.GetFullPath(FileName);
if (!File.Exists(fullPath))
{
// TODO: Localize.
throw new FileNotFoundException($"Csv file '{fullPath}' cannot be found.", fullPath);
}

string tableName = Path.GetFileName(fullPath).Replace('.', '#');

// We can map simplified CSVs to an OLEDB/Text connection, then proceed as normal
using OleDbConnection connection = new();
using OleDbDataAdapter dataAdapter = new();
using OleDbCommandBuilder commandBuilder = new();
using OleDbCommand command = new();

// We have to use the name of the folder which contains the CSV file in the connection string
// If target platform is x64, then use CsvConnectionTemplate64 connection string.
connection.ConnectionString = IntPtr.Size == 8
? string.Format(CultureInfo.InvariantCulture, CsvConnectionTemplate64, Path.GetDirectoryName(fullPath))
: string.Format(CultureInfo.InvariantCulture, CsvConnectionTemplate, Path.GetDirectoryName(fullPath));

// We have to open the connection now, before we try to quote
// the table name, otherwise QuoteIdentifier fails (for OleDb, go figure!)
// The connection will get closed when we dispose of it
connection.Open();

string quotedTableName = commandBuilder.QuoteIdentifier(tableName, connection);

command.Connection = connection;

command.CommandText = $"SELECT * FROM {quotedTableName}";

dataAdapter.SelectCommand = command;

DataTable table = new()
{
Locale = CultureInfo.InvariantCulture,
};

dataAdapter.Fill(table);

object?[][] dataRows = new object?[table.Rows.Count][];
for (int i = 0; i < dataRows.Length; i++)
{
dataRows[i] = [table.Rows[i]];
}

return dataRows;
}

string? ITestDataSource.GetDisplayName(MethodInfo methodInfo, object?[]? data)
// TODO
=> null;
}
9 changes: 9 additions & 0 deletions src/TestFramework/TestFramework.Extensions.Csv/PACKAGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# MSTest.TestFramework

MSTest is Microsoft supported Test Framework.

This package includes the functionality needed for writing Csv-based data source unit tests.

Supported platforms:

- .NET Standard 2.0
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#nullable enable
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#nullable enable
Microsoft.VisualStudio.TestTools.UnitTesting.CsvDataSourceAttribute
Microsoft.VisualStudio.TestTools.UnitTesting.CsvDataSourceAttribute.CsvDataSourceAttribute(string! fileName) -> void
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<UseAssemblyVersion14>true</UseAssemblyVersion14>
</PropertyGroup>

<PropertyGroup>
<IsPackable>true</IsPackable>
<PackageId>MSTest.TestFramework.Csv</PackageId>
<PackageTags>MSTest TestFramework Unittest MSTestV2 Microsoft Test Testing TDD Framework</PackageTags>
<PackageDescription>
MSTest is Microsoft supported Test Framework.

This package includes the functionality needed for writing Csv-based data source unit tests.

Supported platforms:
- .NET Standard 2.0
</PackageDescription>
</PropertyGroup>

<PropertyGroup>
<RootNamespace>Microsoft.VisualStudio.TestTools.UnitTesting</RootNamespace>
<!-- TODO: Discuss the assembly name before shipping/merging -->
<AssemblyName>Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.Csv</AssemblyName>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="$(RepoRoot)src\TestFramework\TestFramework.Extensions\TestFramework.Extensions.csproj" />

<!--<PackageReference Include="System.Data.Odbc" />-->
<PackageReference Include="System.Data.OleDb" />
<!--<PackageReference Include="Microsoft.Data.SqlClient" />-->
</ItemGroup>

<ItemGroup>
<!-- API that is common to all frameworks that we build for. -->
<AdditionalFiles Include="PublicAPI\PublicAPI.Shipped.txt" />
<AdditionalFiles Include="PublicAPI\PublicAPI.Unshipped.txt" />
</ItemGroup>

</Project>

0 comments on commit ccb4a69

Please sign in to comment.