├── #framework ├── Results.Web │ ├── .gitignore │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Results.Web.csproj │ ├── Web.Debug.config │ ├── Web.Release.config │ └── Web.config ├── Runner │ ├── CommandLineArguments.cs │ ├── Outputs │ │ ├── Html │ │ │ ├── FormatHelper.cs │ │ │ ├── HtmlHelper.cs │ │ │ ├── HtmlTemplateBase.cs │ │ │ ├── Models │ │ │ │ ├── HtmlBasicModel.cs │ │ │ │ ├── HtmlResultModel.cs │ │ │ │ └── NavigationLinkModel.cs │ │ │ └── Templates │ │ │ │ ├── CustomPages.json │ │ │ │ ├── Introduction.cshtml │ │ │ │ ├── Results.cshtml │ │ │ │ ├── _Layout.cshtml │ │ │ │ ├── css │ │ │ │ ├── main.css │ │ │ │ └── tooltipster.css │ │ │ │ └── js │ │ │ │ ├── jquery.tooltipster.min.js │ │ │ │ ├── main.js │ │ │ │ └── waypoints.min.js │ │ ├── HtmlOutput.cs │ │ ├── IResultOutput.cs │ │ ├── JsonOutput.cs │ │ └── ResultForAssembly.cs │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Runner.csproj │ ├── Sources │ │ ├── 1_GeneralInfoTableSource.cs │ │ ├── 2_NetFxSupportTableSource.cs │ │ ├── 3_FeatureTestTableSource.cs │ │ ├── FeatureTestSupport │ │ │ ├── AttributeTextCleaner.cs │ │ │ ├── ExceptionNormalizer.cs │ │ │ ├── FeatureTestResult.cs │ │ │ ├── FeatureTestResultKind.cs │ │ │ ├── FeatureTestRun.cs │ │ │ └── FeatureTestRunner.cs │ │ ├── IFeatureTableSource.cs │ │ ├── MetadataKeys.cs │ │ └── MetadataSupport │ │ │ ├── FlatFileCacheStore.cs │ │ │ ├── HttpDataProvider.cs │ │ │ ├── HttpDataRequestException.cs │ │ │ ├── LicenseInfo.cs │ │ │ ├── LicenseResolver.cs │ │ │ ├── LocalPackageCache.cs │ │ │ ├── NetFxVersionHelper.cs │ │ │ └── NuGetGalleryPackage.cs │ ├── app.config │ ├── packages.config │ └── web.config └── Shared │ ├── DependsOnFeatureAttribute.cs │ ├── DisplayOrderAttribute.cs │ ├── FeatureAttribute.cs │ ├── FeatureScoring.cs │ ├── FeatureScoringAttribute.cs │ ├── GenericApiSupport │ ├── GenericHelper.cs │ ├── GenericPlaceholders.cs │ └── GenericRewritingVisitor.cs │ ├── ILibrary.cs │ ├── InfrastructureSupport │ ├── FeatureTestAttributeHelper.cs │ ├── FeatureTestCommand.cs │ └── LibraryProvider.cs │ ├── LibraryAdapterBase.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── ResultData │ ├── Feature.cs │ ├── FeatureCell.cs │ ├── FeatureState.cs │ └── FeatureTable.cs │ ├── Shared.csproj │ ├── SkipException.cs │ ├── SpecialCaseAttribute.cs │ └── packages.config ├── #generated ├── DependencyInjection.html ├── DependencyInjection.json ├── FeatureTests.css ├── Introduction.html ├── JsonSerializers.html ├── JsonSerializers.json ├── css │ ├── main.css │ └── tooltipster.css └── js │ ├── jquery.sticky.js │ ├── jquery.toc.js │ ├── jquery.tooltipster.min.js │ ├── main.js │ └── waypoints.min.js ├── .gitignore ├── .nuget ├── NuGet.Config ├── NuGet.exe └── NuGet.targets ├── .tgitconfig ├── AppHarbor.sln ├── DependencyInjection.CodeOnlyPackages ├── DependencyInjection.CodeOnlyPackages.csproj ├── Properties │ └── AssemblyInfo.cs ├── TinyIoC.cs └── packages.config ├── DependencyInjection ├── 1_BasicTests.cs ├── 2_ListTests.cs ├── 3_GenericTests.cs ├── 4_OptionalTests.cs ├── 5_FuncTests.cs ├── 6_LazyTests.cs ├── 7_PropertyTests.cs ├── 8_WebRequestTests.cs ├── 9_ConvenienceTests.cs ├── Adapters │ ├── AutofacAdapter.cs │ ├── CastleAdapter.cs │ ├── CatelAdapter.cs │ ├── DryIocAdapter.cs │ ├── DynamoIocAdapter.cs │ ├── EndjinCompositionAdapter.cs │ ├── GraceAdapter.cs │ ├── HaveBoxAdapter.cs │ ├── IfInjectorAdapter.cs │ ├── Interface │ │ ├── ContainerAdapterBase.cs │ │ ├── ContainerAdapterExtensions.cs │ │ └── IContainerAdapter.cs │ ├── LightCoreAdapter.cs │ ├── LightInjectAdapter.cs │ ├── LinFuAdapter.cs │ ├── MefAdapter.cs │ ├── MicroSliverAdapter.cs │ ├── MugenAdapter.cs │ ├── MunqAdapter.cs │ ├── NinjectAdapter.cs │ ├── SimpleInjectorAdapter.cs │ ├── SpeediocAdapter.Disabled.cs │ ├── SpringAdapter.cs │ ├── StashboxAdapter.cs │ ├── StructureMapAdapter.cs │ ├── TinyIoCAdapter.cs │ ├── UnityAdapter.cs │ └── WebRequestSupport │ │ ├── TestHttpApplication.cs │ │ └── WebRequestTestHelper.cs ├── DependencyInjection.csproj ├── Html │ ├── AfterAll.html │ └── Options.json ├── Properties │ └── AssemblyInfo.cs ├── TestTypes │ ├── DisposableService.cs │ ├── GenericService.cs │ ├── GenericServiceWithIService2Constraint.cs │ ├── IGenericService.cs │ ├── IResolvableUnregisteredService.cs │ ├── IService.cs │ ├── IService2.cs │ ├── IServiceWithListDependency.cs │ ├── IUnregisteredService.cs │ ├── IndependentService.cs │ ├── IndependentService2.cs │ ├── ServiceThatCreatesRecursiveArrayDependency.cs │ ├── ServiceWithDependencyAndOptionalInt32Parameter.cs │ ├── ServiceWithDependencyAndOptionalOtherServiceParameter.cs │ ├── ServiceWithDependencyOnServiceWithOtherDependency.cs │ ├── ServiceWithFuncConstructorDependency.cs │ ├── ServiceWithListConstructorDependency.cs │ ├── ServiceWithMultipleConstructors.cs │ ├── ServiceWithRecursiveDependency1.cs │ ├── ServiceWithRecursiveDependency2.cs │ ├── ServiceWithRecursiveLazyDependency1.cs │ ├── ServiceWithRecursiveLazyDependency2.cs │ ├── ServiceWithSimpleConstructorDependency.cs │ ├── ServiceWithSimplePropertyDependency.cs │ └── ServiceWithTwoConstructorDependencies.cs ├── app.config └── packages.config ├── FeatureTests.sln ├── FeatureTests.sln.DotSettings ├── JsonSerializers ├── 1_SimpleTypeTests.cs ├── 2_CollectionTests.cs ├── 3_DictionaryTests.cs ├── 4_ReadOnlyPropertyTests.cs ├── 5_ConstructorTests.cs ├── 6_DynamicTests.cs ├── Adapters │ ├── DataContractJsonSerializerAdapter.cs │ ├── IJsonSerializerAdapter.cs │ ├── JilAdapter.cs │ ├── JsonSerializerAdapterBase.cs │ ├── NetJSONAdapter.cs │ ├── NewtonsoftJsonAdapter.cs │ ├── SerializerAdapterExtensions.cs │ ├── ServiceStackJsonSerializerAdapter.cs │ ├── SystemWeb.cs │ └── fastJSONAdapter.cs ├── Html │ ├── AfterAll.html │ ├── BeforeAll.html │ └── Options.json ├── JsonSerializers.csproj ├── Properties │ └── AssemblyInfo.cs ├── TestTypes │ ├── ClassWithSingleProperty.cs │ ├── RootClassWithCustomConstructor.cs │ ├── RootClassWithSingleProperty.cs │ └── RootClassWithSingleReadOnlyProperty.cs └── packages.config ├── README.md └── RunFeatureTests.bat /#framework/Results.Web/.gitignore: -------------------------------------------------------------------------------- 1 | /*.html 2 | 3 | /*.json 4 | 5 | /*.css 6 | 7 | /css 8 | /js 9 | -------------------------------------------------------------------------------- /#framework/Results.Web/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyCopyright("Copyright © Andrey Shchekin 2013")] 8 | 9 | // Setting ComVisible to false makes the types in this assembly not visible 10 | // to COM components. If you need to access a type in this assembly from 11 | // COM, set the ComVisible attribute to true on that type. 12 | [assembly: ComVisible(false)] 13 | 14 | // The following GUID is for the ID of the typelib if this project is exposed to COM 15 | [assembly: Guid("46089307-fc62-4fcf-8c80-30b6356da88c")] 16 | 17 | // Version information for an assembly consists of the following four values: 18 | // 19 | // Major Version 20 | // Minor Version 21 | // Build Number 22 | // Revision 23 | // 24 | // You can specify all the values or you can default the Revision and Build Numbers 25 | // by using the '*' as shown below: 26 | [assembly: AssemblyVersion("1.0.0.0")] 27 | [assembly: AssemblyFileVersion("1.0.0.0")] 28 | -------------------------------------------------------------------------------- /#framework/Results.Web/Web.Debug.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | -------------------------------------------------------------------------------- /#framework/Results.Web/Web.Release.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | -------------------------------------------------------------------------------- /#framework/Results.Web/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /#framework/Runner/CommandLineArguments.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CommandLine; 5 | using CommandLine.Text; 6 | 7 | namespace FeatureTests.Runner { 8 | public class CommandLineArguments { 9 | [Option('o', "output-path", HelpText = "Output directory (taken from config if not specified).")] 10 | public string OutputPath { get; set; } 11 | 12 | [Option('v', "watch-templates", HelpText = "Watches result templates for changes and auto-updates the results.")] 13 | public bool WatchTemplates { get; set; } 14 | 15 | [HelpOption] 16 | public string GetUsage() { 17 | return HelpText.AutoBuild(this, current => HelpText.DefaultParsingErrorsHandler(this, current)); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /#framework/Runner/Outputs/Html/FormatHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Text.RegularExpressions; 4 | using System.Web; 5 | using FeatureTests.Shared.ResultData; 6 | using MarkdownSharp; 7 | 8 | namespace FeatureTests.Runner.Outputs.Html { 9 | public class FormatHelper { 10 | private static readonly NumberFormatInfo NumberFormat = new NumberFormatInfo { 11 | NumberGroupSeparator = " " 12 | }; 13 | 14 | private readonly Markdown markdown; 15 | 16 | public FormatHelper() { 17 | this.markdown = new Markdown(new MarkdownOptions { AutoHyperlink = true, StrictBoldItalic = false }); 18 | } 19 | 20 | public string GetCssClassesForCell(FeatureCell cell) { 21 | var classes = cell.State.ToString().ToLowerInvariant(); 22 | 23 | if (cell.DisplayValue is int) 24 | classes += " number"; 25 | 26 | if (cell.DisplayValue is DateTimeOffset) 27 | classes += " date"; 28 | 29 | return classes; 30 | } 31 | 32 | public string DisplayValue(object value) { 33 | if (value == null) 34 | return ""; 35 | 36 | if (value is string) 37 | return (string)value; 38 | 39 | if (value is int) 40 | return ((int)value).ToString("N0", NumberFormat); 41 | 42 | if (value is DateTimeOffset) 43 | return ((DateTimeOffset)value).ToString("MMM yyyy", CultureInfo.InvariantCulture).ToLowerInvariant(); 44 | 45 | throw new NotSupportedException(String.Format("Value '{0}' ({1}) is not supported.", value, value.GetType())); 46 | } 47 | 48 | public IHtmlString Description(string text) { 49 | if (string.IsNullOrEmpty(text)) 50 | return null; 51 | 52 | var formatted = this.markdown.Transform(text); 53 | // also link twitter usernames 54 | formatted = Regex.Replace(formatted, @"(?<=[^\w\d])@([\w\d]+)", "@$1"); 55 | 56 | return new HtmlString(formatted); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /#framework/Runner/Outputs/Html/HtmlHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Web; 2 | 3 | namespace FeatureTests.Runner.Outputs.Html { 4 | public class HtmlHelper { 5 | public IHtmlString Raw(string html) { 6 | return new HtmlString(html); 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /#framework/Runner/Outputs/Html/HtmlTemplateBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Web; 6 | using RazorTemplates.Core; 7 | using RazorTemplates.Core.Infrastructure; 8 | 9 | namespace FeatureTests.Runner.Outputs.Html { 10 | public abstract class HtmlTemplateBase : TemplateBase { 11 | protected HtmlTemplateBase() { 12 | this.Html = new HtmlHelper(); 13 | this.Format = new FormatHelper(); 14 | } 15 | 16 | protected HtmlHelper Html { get; private set; } 17 | protected FormatHelper Format { get; private set; } 18 | 19 | public string Body { get; set; } 20 | 21 | public new TModel Model { 22 | get { return base.Model; } 23 | } 24 | 25 | protected IHtmlString RenderBody() { 26 | return new HtmlString(this.Body); 27 | } 28 | 29 | protected new void Write(object value) { 30 | base.Write(HtmlEncode(value)); 31 | } 32 | 33 | protected new void WriteAttribute(string attribute, PositionTagged prefix, PositionTagged suffix, params AttributeValue[] values) { 34 | // skip null attributes 35 | if (values == null || values.All(v => v.Value.Value == null)) 36 | return; 37 | 38 | // special processing for booleans 39 | if (values.Length == 1) { 40 | var value = values[0].Value.Value; 41 | if (value.Equals(false)) 42 | return; 43 | 44 | if (value.Equals(true)) { 45 | base.Write(attribute); 46 | return; 47 | } 48 | } 49 | 50 | base.Write(prefix.Value); 51 | foreach (var attributeValue in values) { 52 | base.Write(attributeValue.Prefix.Value); 53 | //System.Diagnostics.Debugger.Launch(); 54 | this.WriteAttributeValue(attributeValue.Value.Value); 55 | } 56 | base.Write(suffix.Value); 57 | } 58 | 59 | private void WriteAttributeValue(object value) { 60 | if (value == null) 61 | return; 62 | 63 | var encoded = HtmlEncode(value).Replace("&", "&") 64 | .Replace("\"", """); 65 | base.Write(encoded); 66 | } 67 | 68 | private string HtmlEncode(object value) { 69 | if (value == null) 70 | return null; 71 | 72 | var html = value as IHtmlString; 73 | if (html != null) 74 | return html.ToHtmlString(); 75 | 76 | return WebUtility.HtmlEncode(value.ToString()); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /#framework/Runner/Outputs/Html/Models/HtmlBasicModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace FeatureTests.Runner.Outputs.Html.Models { 4 | public class HtmlBasicModel { 5 | public HtmlBasicModel() { 6 | this.Navigation = new List(); 7 | } 8 | 9 | public string Title { get; set; } 10 | public IList Navigation { get; private set; } 11 | } 12 | } -------------------------------------------------------------------------------- /#framework/Runner/Outputs/Html/Models/HtmlResultModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using FeatureTests.Shared.ResultData; 5 | 6 | namespace FeatureTests.Runner.Outputs.Html.Models { 7 | public class HtmlResultModel : HtmlBasicModel { 8 | public HtmlResultModel(IReadOnlyList tables) { 9 | this.Tables = tables.ToList(); 10 | this.TableIdMap = new Dictionary(); 11 | } 12 | 13 | public IList Tables { get; private set; } 14 | public IDictionary TableIdMap { get; set; } 15 | 16 | public string HtmlBeforeAll { get; set; } 17 | public string HtmlAfterAll { get; set; } 18 | public bool TotalVisible { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /#framework/Runner/Outputs/Html/Models/NavigationLinkModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using AshMind.Extensions; 5 | 6 | namespace FeatureTests.Runner.Outputs.Html.Models { 7 | public class NavigationLinkModel { 8 | public NavigationLinkModel(string name, string url, bool onCurrentPage, IEnumerable childLinks = null) { 9 | this.Name = name; 10 | this.Url = url; 11 | this.OnCurrentPage = onCurrentPage; 12 | this.ChildLinks = childLinks.EmptyIfNull().ToList(); 13 | } 14 | 15 | public string Url { get; set; } 16 | public string Name { get; set; } 17 | public bool OnCurrentPage { get; set; } 18 | public IList ChildLinks { get; private set; } 19 | } 20 | } -------------------------------------------------------------------------------- /#framework/Runner/Outputs/Html/Templates/CustomPages.json: -------------------------------------------------------------------------------- 1 | [{ "Title": "Introduction", "Name": "Introduction" }] -------------------------------------------------------------------------------- /#framework/Runner/Outputs/Html/Templates/Introduction.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Linq 2 | @inherits FeatureTests.Runner.Outputs.Html.HtmlTemplateBase 3 | 4 |

5 | This project monitors support for different features across multiple libraries.
6 | The goal is to encourage feature parity and better understanding of what is and should be available. 7 |

8 | 9 |

You can find the code behind this site at net-feature-tests GitHub project.

10 | 11 |

Please note the following:

12 |
    13 |
  1. The list of features is based on what I consider to be important. It is not an objective measure on which library is "better".
  2. 14 |
  3. It is not possible to test for all features automatically. For example, extensibility is very important, but impossible to test/measure.
  4. 15 |
  5. The test about might not necessary be up to date (see the version for each library).
  6. 16 |
  7. The test code should not be seen as best practice for each library — it forces all of them to the same test interface.
  8. 17 |
  9. I am not an expert on all libraries and not all adapters were reviewed by authors. So feature may fail because of incorrect adapter.
  10. 18 |
19 | 20 |

Thanks for reading!

-------------------------------------------------------------------------------- /#framework/Runner/Outputs/Html/Templates/Results.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Linq 2 | @using AshMind.Extensions 3 | @using FeatureTests.Runner.Sources 4 | @inherits FeatureTests.Runner.Outputs.Html.HtmlTemplateBase 5 | 6 | @Html.Raw(Model.HtmlBeforeAll) 7 | 8 | @foreach (var table in Model.Tables) { 9 |
10 |

@table.DisplayName

11 |
@Format.Description(table.Description)
12 | 13 | 14 | 15 | 16 | @foreach (var library in table.Libraries) { 17 | 18 | } 19 | 20 | 21 | @foreach (var row in table.GetFeatureRows()) { 22 | var feature = row.Item1; 23 | var rowClass = (string)null; 24 | if (feature.Key == MetadataKeys.TotalFeature && !Model.TotalVisible) { 25 | rowClass = "total-hidden"; 26 | } 27 | 28 | 29 | 30 | @foreach (var cell in row.Item2) { 31 | 42 | } 43 | 44 | } 45 |
Name@library.Name
@feature.Name 32 | @if (cell.HasDetails) { 33 | @Format.DisplayValue(cell.DisplayValue) 34 | } 35 | else if (cell.DisplayUri != null) { 36 | @Format.DisplayValue(cell.DisplayValue) 37 | } 38 | else { 39 | @Format.DisplayValue(cell.DisplayValue) 40 | } 41 |
46 |
47 | } 48 | 49 | @Html.Raw(Model.HtmlAfterAll) 50 | 51 | -------------------------------------------------------------------------------- /#framework/Runner/Outputs/Html/Templates/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Linq 2 | @inherits FeatureTests.Runner.Outputs.Html.HtmlTemplateBase 3 | 4 | 5 | @* Metanote: Ignore the following note in Razor template, it is only relevant to the result. *@ 6 | 7 | 8 | 9 | @Model.Title 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 40 | 41 |
42 |

@Model.Title

43 | 44 | @RenderBody() 45 |
46 | 47 | 48 | 49 | 50 | 51 | 52 | 62 | 63 | -------------------------------------------------------------------------------- /#framework/Runner/Outputs/IResultOutput.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace FeatureTests.Runner.Outputs { 7 | public interface IResultOutput : IDisposable { 8 | void Write(DirectoryInfo outputDirectory, IReadOnlyCollection results, bool keepUpdatingIfTemplatesChange = false); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /#framework/Runner/Outputs/JsonOutput.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Runtime.Versioning; 7 | using FeatureTests.Shared.ResultData; 8 | using Newtonsoft.Json; 9 | using NuGet; 10 | using FeatureTests.Runner.Sources; 11 | using FeatureTests.Shared; 12 | 13 | namespace FeatureTests.Runner.Outputs { 14 | public class JsonOutput : IResultOutput { 15 | public void Write(DirectoryInfo outputDirectory, IReadOnlyCollection results, bool keepUpdatingIfTemplatesChange = false) { 16 | foreach (var result in results) { 17 | Write(outputDirectory, result); 18 | } 19 | } 20 | 21 | private void Write(DirectoryInfo outputDirectory, ResultForAssembly result) { 22 | var tableList = result.Tables.ToArray(); 23 | 24 | var general = tableList.First(t => t.Key == MetadataKeys.GeneralInfoTable); 25 | var netFxVersions = tableList.First(t => t.Key == MetadataKeys.NetFxSupportTable); 26 | var data = tableList[0].Libraries.Select(l => new { 27 | name = l.Name, 28 | url = general[l, MetadataKeys.UrlFeature].DisplayUri, 29 | version = general[l, MetadataKeys.VersionFeature].DisplayValue, 30 | supports = GetNetFxVersions(l, netFxVersions), 31 | features = GetAllFeatureData(l, tableList) 32 | }); 33 | 34 | var json = JsonConvert.SerializeObject(data, Formatting.Indented, new JsonSerializerSettings { 35 | NullValueHandling = NullValueHandling.Ignore 36 | }); 37 | File.WriteAllText(Path.Combine(outputDirectory.FullName, result.OutputNamePrefix + ".json"), json); 38 | } 39 | 40 | private string[] GetNetFxVersions(ILibrary library, FeatureTable table) { 41 | return table.Features.Where(f => table[library, f].State == FeatureState.Success) 42 | .Select(f => (IGrouping)f.Key) 43 | .Select(g => VersionUtility.GetShortFrameworkName(g.First())) 44 | .ToArray(); 45 | } 46 | 47 | private IDictionary GetAllFeatureData(ILibrary library, IEnumerable tables) { 48 | var featuresByName = tables.Where(t => t.Key != MetadataKeys.GeneralInfoTable && t.Key != MetadataKeys.NetFxSupportTable) 49 | .SelectMany(t => t.Features.Select(f => this.GetSingleFeatureData(f, t[library, f]))) 50 | .GroupBy(p => p.Key) 51 | .ToArray(); 52 | 53 | var duplicate = featuresByName.FirstOrDefault(g => g.Count() > 1); 54 | if (duplicate != null) 55 | throw new Exception("Feature name " + duplicate.Key + " was used more than once."); 56 | 57 | return featuresByName.ToDictionary(p => p.Key, p => p.Single().Value); 58 | } 59 | 60 | private KeyValuePair GetSingleFeatureData(Feature feature, FeatureCell cell) { 61 | var key = this.GetFeatureName(feature); 62 | var value = new { 63 | result = cell.State.ToString().ToLowerInvariant(), 64 | comment = cell.Details, 65 | error = cell.RawError 66 | }; 67 | 68 | return new KeyValuePair(key, value); 69 | } 70 | 71 | private string GetFeatureName(Feature feature) { 72 | // HACK :) 73 | var method = feature.Key as MethodInfo; 74 | return method != null ? method.Name : feature.Name; 75 | } 76 | 77 | #region IDisposable Members 78 | 79 | public void Dispose() { 80 | } 81 | 82 | #endregion 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /#framework/Runner/Outputs/ResultForAssembly.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reflection; 3 | using FeatureTests.Shared.ResultData; 4 | 5 | namespace FeatureTests.Runner.Outputs { 6 | public class ResultForAssembly { 7 | public ResultForAssembly(Assembly assembly, IReadOnlyList tables, string outputNamePrefix) { 8 | this.Assembly = assembly; 9 | this.Tables = tables; 10 | this.OutputNamePrefix = outputNamePrefix; 11 | } 12 | 13 | public Assembly Assembly { get; private set; } 14 | public IReadOnlyList Tables { get; private set; } 15 | public string OutputNamePrefix { get; private set; } 16 | } 17 | } -------------------------------------------------------------------------------- /#framework/Runner/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyCopyright("Copyright © Andrey Shchekin 2013")] 8 | 9 | // Setting ComVisible to false makes the types in this assembly not visible 10 | // to COM components. If you need to access a type in this assembly from 11 | // COM, set the ComVisible attribute to true on that type. 12 | [assembly: ComVisible(false)] 13 | 14 | // The following GUID is for the ID of the typelib if this project is exposed to COM 15 | [assembly: Guid("0f34cfb0-7802-4f0a-a540-c82d9a9bec0f")] 16 | 17 | // Version information for an assembly consists of the following four values: 18 | // 19 | // Major Version 20 | // Minor Version 21 | // Build Number 22 | // Revision 23 | // 24 | // You can specify all the values or you can default the Build and Revision Numbers 25 | // by using the '*' as shown below: 26 | // [assembly: AssemblyVersion("1.0.*")] 27 | [assembly: AssemblyVersion("1.0.0.0")] 28 | [assembly: AssemblyFileVersion("1.0.0.0")] 29 | -------------------------------------------------------------------------------- /#framework/Runner/Sources/2_NetFxSupportTableSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Runtime.Versioning; 6 | using FeatureTests.Shared.ResultData; 7 | using NuGet; 8 | using FeatureTests.Shared; 9 | using FeatureTests.Shared.InfrastructureSupport; 10 | using FeatureTests.Runner.Sources.MetadataSupport; 11 | 12 | namespace FeatureTests.Runner.Sources { 13 | public class NetFxSupportTableSource : IFeatureTableSource { 14 | private readonly LocalPackageCache packageCache; 15 | 16 | public NetFxSupportTableSource(LocalPackageCache packageCache) { 17 | this.packageCache = packageCache; 18 | } 19 | 20 | public IEnumerable GetTables(Assembly featureTestAssembly) { 21 | yield return this.GetTable(featureTestAssembly); 22 | } 23 | 24 | private FeatureTable GetTable(Assembly featureTestAssembly) { 25 | var libraries = LibraryProvider.GetAdapters(featureTestAssembly).ToArray(); 26 | var allVersionsGrouped = libraries.Where(l => l.PackageId != null) 27 | .Select(l => this.packageCache.GetPackage(l.PackageId)) 28 | .SelectMany(p => p.GetSupportedFrameworks()) 29 | .SelectMany(NetFxVersionHelper.Split) 30 | .Where(NetFxVersionHelper.ShouldDisplay) 31 | .GroupBy(NetFxVersionHelper.GetDisplayName) 32 | .OrderBy(g => NetFxVersionHelper.GetDisplayOrder(g.First())) 33 | .ToList(); 34 | 35 | var versionFeatures = allVersionsGrouped.Select(g => new Feature(g, g.Key)); 36 | var table = new FeatureTable(MetadataKeys.NetFxSupportTable, "Supported .NET versions", libraries, versionFeatures) { 37 | Description = "This information is based on versions included in NuGet package.", 38 | Scoring = FeatureScoring.NotScored 39 | }; 40 | 41 | foreach (var library in libraries) { 42 | this.FillNetVersionSupport(table, library, allVersionsGrouped); 43 | } 44 | 45 | return table; 46 | } 47 | 48 | private void FillNetVersionSupport(FeatureTable table, ILibrary library, IEnumerable> allVersionsGrouped) { 49 | if (library.PackageId == null) { 50 | // this is always intentional 51 | foreach (var versionGroup in allVersionsGrouped) { 52 | var cell = table[library, versionGroup]; 53 | cell.State = FeatureState.Skipped; 54 | cell.DisplayValue = "n/a"; 55 | } 56 | 57 | return; 58 | } 59 | 60 | var package = this.packageCache.GetPackage(library.PackageId); 61 | var supported = package.GetSupportedFrameworks().ToArray(); 62 | 63 | foreach (var versionGroup in allVersionsGrouped) { 64 | var cell = table[library, versionGroup]; 65 | if (versionGroup.Any(v => VersionUtility.IsCompatible(v, supported))) { 66 | cell.State = FeatureState.Success; 67 | cell.DisplayValue = "yes"; 68 | } 69 | else { 70 | cell.State = FeatureState.Concern; 71 | cell.DisplayValue = "no"; 72 | } 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /#framework/Runner/Sources/FeatureTestSupport/AttributeTextCleaner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | using AshMind.Extensions; 6 | 7 | namespace FeatureTests.Runner.Sources.FeatureTestSupport { 8 | public class AttributeTextCleaner { 9 | public string CleanWhitespace(string attributeText) { 10 | if (attributeText.IsNullOrEmpty()) 11 | return attributeText; 12 | 13 | // remove all spaces at the start of the line 14 | return Regex.Replace(attributeText, @"^ +", "", RegexOptions.Multiline); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /#framework/Runner/Sources/FeatureTestSupport/FeatureTestResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FeatureTests.Runner.Sources.FeatureTestSupport { 4 | public class FeatureTestResult { 5 | public FeatureTestResultKind Kind { get; private set; } 6 | public string Message { get; private set; } 7 | public Exception Exception { get; private set; } 8 | 9 | public FeatureTestResult(FeatureTestResultKind kind, string message = null, Exception exception = null) { 10 | this.Kind = kind; 11 | this.Message = message; 12 | this.Exception = exception; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /#framework/Runner/Sources/FeatureTestSupport/FeatureTestResultKind.cs: -------------------------------------------------------------------------------- 1 | namespace FeatureTests.Runner.Sources.FeatureTestSupport { 2 | public enum FeatureTestResultKind { 3 | Success, 4 | SkippedDueToDependency, 5 | SkippedDueToSpecialCase, 6 | Failure 7 | } 8 | } -------------------------------------------------------------------------------- /#framework/Runner/Sources/FeatureTestSupport/FeatureTestRun.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Threading.Tasks; 4 | 5 | namespace FeatureTests.Runner.Sources.FeatureTestSupport { 6 | public class FeatureTestRun { 7 | public MethodInfo Method { get; private set; } 8 | public Type AdapterType { get; private set; } 9 | public Task Task { get; private set; } 10 | 11 | public FeatureTestRun(MethodInfo method, Type adapterType, Task task) { 12 | this.Method = method; 13 | this.AdapterType = adapterType; 14 | this.Task = task; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /#framework/Runner/Sources/IFeatureTableSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using FeatureTests.Shared.ResultData; 6 | 7 | namespace FeatureTests.Runner.Sources { 8 | public interface IFeatureTableSource { 9 | IEnumerable GetTables(Assembly featureTestAssembly); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /#framework/Runner/Sources/MetadataKeys.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace FeatureTests.Runner.Sources { 6 | public static class MetadataKeys { 7 | public static readonly object GeneralInfoTable = "Metadata.GeneralInfo"; 8 | public static readonly object VersionFeature = "Metadata.Version"; 9 | public static readonly object UrlFeature = "Metadata.Url"; 10 | public static readonly object NetFxSupportTable = "Metadata.NetFxSupport"; 11 | public static readonly object TotalFeature = "Metadata.Total"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /#framework/Runner/Sources/MetadataSupport/HttpDataProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net.Http; 6 | using System.Threading.Tasks; 7 | using CacheCow.Client; 8 | 9 | namespace FeatureTests.Runner.Sources.MetadataSupport { 10 | public class HttpDataProvider { 11 | private readonly DirectoryInfo cacheRoot; 12 | 13 | public HttpDataProvider(DirectoryInfo cacheRoot) { 14 | this.cacheRoot = cacheRoot; 15 | } 16 | 17 | public async Task GetDataAsync(Uri url) { 18 | using (var client = CreateHttpClient()) { 19 | var response = await client.GetAsync(url); 20 | response.EnsureSuccessStatusCode(); 21 | 22 | return await response.Content.ReadAsAsync(); 23 | } 24 | } 25 | 26 | public async Task GetStringAsync(Uri url) { 27 | using (var client = CreateHttpClient()) 28 | using (var response = await client.GetAsync(url)) { 29 | if (!response.IsSuccessStatusCode) 30 | throw new HttpDataRequestException(response.StatusCode); 31 | 32 | return await response.Content.ReadAsStringAsync(); 33 | } 34 | } 35 | 36 | public HttpClient CreateHttpClient() { 37 | var handler = new CachingHandler(new FlatFileCacheStore(cacheRoot)) { 38 | InnerHandler = new HttpClientHandler() 39 | }; 40 | var baseValidator = handler.ResponseValidator; 41 | handler.ResponseValidator = m => { 42 | var result = baseValidator(m); 43 | if (result == ResponseValidationResult.NotExist || result == ResponseValidationResult.OK) 44 | return result; 45 | 46 | var sinceName = "X-Forced-Cache-Since"; 47 | var sinceString = m.Headers.Contains(sinceName) 48 | ? m.Headers.GetValues(sinceName).SingleOrDefault() 49 | : null; 50 | if (sinceString == null) { 51 | m.Headers.Add(sinceName, DateTimeOffset.Now.ToString()); 52 | return ResponseValidationResult.OK; 53 | } 54 | 55 | var since = DateTimeOffset.Parse(sinceString); 56 | return (DateTimeOffset.Now - since).TotalHours <= 1 57 | ? ResponseValidationResult.OK 58 | : ResponseValidationResult.Stale; 59 | }; 60 | 61 | return new HttpClient(handler); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /#framework/Runner/Sources/MetadataSupport/HttpDataRequestException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Runtime.Serialization; 6 | 7 | namespace FeatureTests.Runner.Sources.MetadataSupport { 8 | [Serializable] 9 | public class HttpDataRequestException : Exception { 10 | public HttpStatusCode StatusCode { get; private set; } 11 | 12 | public HttpDataRequestException(HttpStatusCode statusCode) { 13 | StatusCode = statusCode; 14 | } 15 | 16 | public HttpDataRequestException(string message, HttpStatusCode statusCode) 17 | : base(message) 18 | { 19 | StatusCode = statusCode; 20 | } 21 | 22 | public HttpDataRequestException(string message, HttpStatusCode statusCode, Exception inner) 23 | : base(message, inner) 24 | { 25 | StatusCode = statusCode; 26 | } 27 | 28 | protected HttpDataRequestException(SerializationInfo info, StreamingContext context) : base(info, context) { } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /#framework/Runner/Sources/MetadataSupport/LicenseInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace FeatureTests.Runner.Sources.MetadataSupport { 6 | public class LicenseInfo { 7 | public LicenseInfo() { 8 | this.Urls = new List(); 9 | } 10 | 11 | public string ShortName { get; set; } 12 | public string Pattern { get; set; } 13 | public IList Urls { get; private set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /#framework/Runner/Sources/MetadataSupport/LicenseResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | using System.Threading.Tasks; 7 | using Newtonsoft.Json; 8 | 9 | namespace FeatureTests.Runner.Sources.MetadataSupport { 10 | public class LicenseResolver { 11 | #region LicenseRoot Class 12 | private class LicenseRoot { 13 | public LicenseInfo[] Licenses { get; set; } 14 | } 15 | #endregion 16 | 17 | private readonly HttpDataProvider httpProvider; 18 | private readonly IList licenses; 19 | 20 | public LicenseResolver(HttpDataProvider httpProvider, Uri licensesJsonUrl) { 21 | this.httpProvider = httpProvider; 22 | 23 | var licensesJson = this.httpProvider.GetStringAsync(licensesJsonUrl).Result; 24 | this.licenses = JsonConvert.DeserializeObject(licensesJson).Licenses; 25 | } 26 | 27 | public async Task GetLicenseInfo(Uri licenseUrl) { 28 | var licenseInfo = this.licenses.FirstOrDefault(l => l.Urls.Contains(licenseUrl)); 29 | if (licenseInfo != null) 30 | return licenseInfo; 31 | 32 | var licenseText = await httpProvider.GetStringAsync(licenseUrl); 33 | licenseText = Regex.Replace(licenseText, "<[^>]+>", ""); // in case it is HTML -- naive but let's not overengineer 34 | licenseInfo = this.licenses.Where(l => l.Pattern != null) 35 | .FirstOrDefault(l => IsMatch(l, licenseText)); 36 | 37 | return licenseInfo; 38 | } 39 | 40 | private bool IsMatch(LicenseInfo licenseInfo, string licenseText) { 41 | var pattern = Regex.Replace(licenseInfo.Pattern, @"\s+", "\\s+"); 42 | return Regex.IsMatch(licenseText, pattern, RegexOptions.Singleline | RegexOptions.IgnoreCase); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /#framework/Runner/Sources/MetadataSupport/LocalPackageCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using NuGet; 6 | 7 | namespace FeatureTests.Runner.Sources.MetadataSupport { 8 | public class LocalPackageCache { 9 | private readonly IPackageRepository packageRepository; 10 | private readonly ConcurrentDictionary packages = new ConcurrentDictionary(); 11 | 12 | public LocalPackageCache(string nugetPackagesPath) { 13 | this.packageRepository = new SharedPackageRepository(nugetPackagesPath); 14 | } 15 | 16 | public IPackage GetPackage(string packageId) { 17 | return this.packages.GetOrAdd(packageId, _ => { 18 | var found = this.packageRepository.FindPackagesById(packageId).ToArray(); 19 | if (found.Length > 1) { 20 | if (packageId == "ValueInjecter") { // special case/hack, remove when ValueInjecter is fixed 21 | var correct = found.SingleOrDefault(p => p.Version.ToString() == "2.3.3"); 22 | if (correct != null) 23 | return correct; 24 | } 25 | 26 | throw new Exception(string.Format("Found more than one package '{0}' in '{1}': check if any of those are obsolete/unused.", packageId, this.packageRepository.Source)); 27 | } 28 | 29 | if (found.Length == 0) 30 | throw new Exception(string.Format("Package '{0}' was not found in '{1}'.", packageId, this.packageRepository.Source)); 31 | 32 | return found[0]; 33 | }); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /#framework/Runner/Sources/MetadataSupport/NuGetGalleryPackage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace FeatureTests.Runner.Sources.MetadataSupport { 6 | public class NuGetGalleryPackage { 7 | public DateTime Published { get; set; } 8 | public int DownloadCount { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /#framework/Runner/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /#framework/Runner/web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /#framework/Shared/DependsOnFeatureAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace FeatureTests.Shared { 6 | /// 7 | /// Defines a feature that must succeed for this test to be run. 8 | /// This is not currently used by Xunit -- only by FeatureTests.Runner. 9 | /// 10 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] 11 | public class DependsOnFeatureAttribute : Attribute { 12 | public Type DeclaringType { get; private set; } 13 | public string MethodName { get; private set; } 14 | 15 | public DependsOnFeatureAttribute(Type declaringType, string methodName) { 16 | this.MethodName = methodName; 17 | this.DeclaringType = declaringType; 18 | } 19 | 20 | // DeclaringType not provided = current type. 21 | public DependsOnFeatureAttribute(string methodName) { 22 | this.MethodName = methodName; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /#framework/Shared/DisplayOrderAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace FeatureTests.Shared { 6 | [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] 7 | public class DisplayOrderAttribute : Attribute { 8 | public int Order { get; private set; } 9 | 10 | public DisplayOrderAttribute(int order) { 11 | this.Order = order; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /#framework/Shared/FeatureAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using FeatureTests.Shared.InfrastructureSupport; 5 | using Xunit; 6 | using Xunit.Sdk; 7 | 8 | namespace FeatureTests.Shared { 9 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] 10 | public class FeatureAttribute : FactAttribute { 11 | protected override IEnumerable EnumerateTestCommands(IMethodInfo method) { 12 | return LibraryProvider.GetAdapters(method.Class.Type.Assembly) 13 | .Select(adapter => new FeatureTestCommand(method, adapter)); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /#framework/Shared/FeatureScoring.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace FeatureTests.Shared { 6 | public enum FeatureScoring { 7 | NotScored, 8 | SinglePoint, 9 | PointPerFeature 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /#framework/Shared/FeatureScoringAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace FeatureTests.Shared { 6 | [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] 7 | public class FeatureScoringAttribute : Attribute { 8 | public FeatureScoring Value { get; private set; } 9 | 10 | public FeatureScoringAttribute(FeatureScoring value) { 11 | this.Value = value; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /#framework/Shared/GenericApiSupport/GenericHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text.RegularExpressions; 6 | using AshMind.Extensions; 7 | using FeatureTests.Shared.GenericApiSupport.GenericPlaceholders; 8 | 9 | namespace FeatureTests.Shared.GenericApiSupport { 10 | public static class GenericHelper { 11 | public static void RewriteAndInvoke(Expression call, params Type[] genericArguments) { 12 | var rewritten = Rewrite(call, genericArguments); 13 | ((Action)rewritten.Compile())(); 14 | } 15 | 16 | public static object RewriteAndInvoke(Expression> call, params Type[] genericArguments) { 17 | var rewritten = Rewrite(call, genericArguments); 18 | return ((Func)rewritten.Compile())(); 19 | } 20 | 21 | private static LambdaExpression Rewrite(Expression call, Type[] genericArguments) { 22 | var visitor = new GenericRewritingVisitor(a => RewriteTypeIfPossible(a, genericArguments)); 23 | return (LambdaExpression)visitor.Visit(call); 24 | } 25 | 26 | private static Type RewriteTypeIfPossible(Type type, Type[] rewriteTo) { 27 | if (type.IsDefined()) 28 | return RewritePlaceholderType(type, rewriteTo); 29 | 30 | if (!type.IsGenericType) 31 | return type; 32 | 33 | var arguments = type.GetGenericArguments(); 34 | var rewritten = arguments.Select(a => RewriteTypeIfPossible(a, rewriteTo)).ToArray(); 35 | if (!rewritten.SequenceEqual(arguments)) 36 | return type.GetGenericTypeDefinition().MakeGenericType(rewritten); 37 | 38 | return type; 39 | } 40 | 41 | private static Type RewritePlaceholderType(Type type, Type[] rewriteTo) { 42 | if (type.IsGenericType) 43 | type = type.GetGenericArguments()[0]; 44 | 45 | var index = int.Parse(Regex.Match(type.Name, @"\d+").Value) - 1; 46 | return rewriteTo[index]; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /#framework/Shared/GenericApiSupport/GenericPlaceholders.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | // ReSharper disable CheckNamespace 6 | namespace FeatureTests.Shared.GenericApiSupport.GenericPlaceholders { 7 | // ReSharper restore CheckNamespace 8 | 9 | [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, AllowMultiple = false)] 10 | public class GenericPlaceholderAttribute : Attribute {} 11 | 12 | [GenericPlaceholder] public class X1 {} 13 | [GenericPlaceholder] public class X2 {} 14 | 15 | // ReSharper disable UnusedTypeParameter 16 | 17 | // Those classes provide hierarchy used to match generic inheritance constraints. 18 | [GenericPlaceholder] public class P {} 19 | [GenericPlaceholder] public class C : P {} 20 | 21 | // ReSharper restore UnusedTypeParameter 22 | } 23 | -------------------------------------------------------------------------------- /#framework/Shared/ILibrary.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace FeatureTests.Shared { 4 | public interface ILibrary { 5 | string Name { get; } 6 | Assembly Assembly { get; } 7 | string PackageId { get; } 8 | } 9 | } -------------------------------------------------------------------------------- /#framework/Shared/InfrastructureSupport/FeatureTestAttributeHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace FeatureTests.Shared.InfrastructureSupport { 8 | // in real application it should not be static or helper 9 | public static class FeatureTestAttributeHelper { 10 | public static string GetDisplayName(MemberInfo member) { 11 | var displayNameAttribute = member.GetCustomAttributes().SingleOrDefault(); 12 | if (displayNameAttribute == null) 13 | return member.Name; 14 | 15 | return displayNameAttribute.DisplayName; 16 | } 17 | 18 | public static int GetDisplayOrder(MemberInfo member) { 19 | var displayOrderAttribute = member.GetCustomAttributes().SingleOrDefault(); 20 | if (displayOrderAttribute == null) 21 | return int.MaxValue; 22 | 23 | return displayOrderAttribute.Order; 24 | } 25 | 26 | public static FeatureScoring GetScoring(MemberInfo member) { 27 | var scoringAttribute = member.GetCustomAttributes().SingleOrDefault(); 28 | if (scoringAttribute == null) 29 | return FeatureScoring.PointPerFeature; 30 | 31 | return (FeatureScoring)scoringAttribute.Value; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /#framework/Shared/InfrastructureSupport/FeatureTestCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using Xunit.Sdk; 4 | 5 | namespace FeatureTests.Shared.InfrastructureSupport { 6 | public class FeatureTestCommand : TestCommand { 7 | private readonly ILibrary adapter; 8 | 9 | public FeatureTestCommand(IMethodInfo method, ILibrary adapter) 10 | : base(method, adapter.Name, MethodUtility.GetTimeoutParameter(method)) 11 | { 12 | this.adapter = adapter; 13 | } 14 | 15 | public override MethodResult Execute(object testClass) { 16 | try { 17 | var parameters = this.testMethod.MethodInfo.GetParameters(); 18 | if (parameters.Length != 1) 19 | throw new InvalidOperationException("Feature test method must have a single parameter."); 20 | 21 | this.testMethod.Invoke(testClass, new[] {this.adapter}); 22 | } 23 | catch (TargetInvocationException ex) { 24 | ExceptionUtility.RethrowWithNoStackTraceLoss(ex.InnerException); 25 | } 26 | catch (SkipException ex) { 27 | return new SkipResult(this.testMethod, this.DisplayName, ex.Message); 28 | } 29 | 30 | return new PassedResult(this.testMethod, this.DisplayName); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /#framework/Shared/InfrastructureSupport/LibraryProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | using AshMind.Extensions; 7 | 8 | namespace FeatureTests.Shared.InfrastructureSupport { 9 | public static class LibraryProvider { 10 | private static readonly ConcurrentDictionary cache = new ConcurrentDictionary(); 11 | 12 | public static IReadOnlyCollection GetAdapterTypes(Assembly assembly) { 13 | return cache.GetOrAdd(assembly, 14 | a => assembly.GetTypes() 15 | .Where(t => t.HasInterface() && !t.IsAbstract) 16 | .OrderBy(t => t.Name) 17 | .ToArray() 18 | ); 19 | } 20 | 21 | public static IEnumerable GetAdapters(Assembly assembly) { 22 | return GetAdapterTypes(assembly).Select(CreateAdapter); 23 | } 24 | 25 | public static ILibrary CreateAdapter(Type adapterType) { 26 | return (ILibrary)Activator.CreateInstance(adapterType); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /#framework/Shared/LibraryAdapterBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text.RegularExpressions; 6 | using JetBrains.Annotations; 7 | 8 | namespace FeatureTests.Shared { 9 | [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] 10 | public abstract class LibraryAdapterBase : ILibrary { 11 | public virtual string Name { 12 | get { return Regex.Match(GetType().Name, "^(.+?)(?:Adapter)?$").Groups[1].Value; } 13 | } 14 | 15 | [CanBeNull] 16 | public abstract Assembly Assembly { get; } 17 | 18 | public virtual string PackageId { 19 | get { return Assembly.GetName().Name; } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /#framework/Shared/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("FeatureTests.Support")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("FeatureTests.Support")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("a47121b3-99e2-49d9-a0ab-9e5cd3ede4ec")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /#framework/Shared/ResultData/Feature.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace FeatureTests.Shared.ResultData { 6 | public class Feature { 7 | public Feature(string name) : this(new object(), name) { 8 | } 9 | 10 | public Feature(object key, string name) { 11 | this.Key = key; 12 | this.Name = name; 13 | } 14 | 15 | public object Key { get; private set; } 16 | public string Name { get; private set; } 17 | public string Description { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /#framework/Shared/ResultData/FeatureCell.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace FeatureTests.Shared.ResultData { 4 | public class FeatureCell { 5 | public object DisplayValue { get; set; } 6 | public Uri DisplayUri { get; set; } 7 | public string Details { get; set; } 8 | public FeatureState State { get; set; } 9 | 10 | public bool HasDetails { 11 | get { return !string.IsNullOrEmpty(this.Details); } 12 | } 13 | 14 | public string RawError { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /#framework/Shared/ResultData/FeatureState.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace FeatureTests.Shared.ResultData { 6 | public enum FeatureState { 7 | Neutral, 8 | Success, 9 | Skipped, 10 | Concern, 11 | Failure 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /#framework/Shared/ResultData/FeatureTable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using AshMind.Extensions; 5 | using Newtonsoft.Json; 6 | 7 | namespace FeatureTests.Shared.ResultData { 8 | public class FeatureTable { 9 | private readonly IReadOnlyDictionary, FeatureCell> cells; 10 | 11 | public FeatureTable(string displayName, IEnumerable libraries, IEnumerable features) 12 | : this(new object(), displayName, libraries, features) 13 | { 14 | } 15 | 16 | public FeatureTable(object key, string displayName, IEnumerable libraries, IEnumerable features) { 17 | this.Key = key; 18 | this.DisplayName = displayName; 19 | this.Libraries = (libraries as IList ?? libraries.ToList()).AsReadOnly(); 20 | this.Features = (features as IList ?? features.ToList()).AsReadOnly(); 21 | this.Scoring = FeatureScoring.PointPerFeature; 22 | this.cells = ( 23 | from adapter in this.Libraries 24 | from feature in this.Features 25 | select Tuple.Create(adapter.Name, feature.Key) 26 | ).ToDictionary(t => t, t => new FeatureCell()); 27 | } 28 | 29 | public object Key { get; private set; } 30 | public string DisplayName { get; private set; } 31 | public FeatureScoring Scoring { get; set; } 32 | public string Description { get; set; } 33 | public IReadOnlyCollection Libraries { get; private set; } 34 | public IReadOnlyCollection Features { get; private set; } 35 | 36 | public FeatureCell this[ILibrary library, Feature feature] { 37 | get { return this[library, feature.Key]; } 38 | } 39 | 40 | public FeatureCell this[ILibrary library, object featureKey] { 41 | get { return this.cells[Tuple.Create(library.Name, featureKey)]; } 42 | } 43 | 44 | public IEnumerable>> GetFeatureRows() { 45 | return from feature in this.Features 46 | select Tuple.Create(feature, from library in this.Libraries select this[library, feature]); 47 | } 48 | 49 | public int GetScore(ILibrary library) { 50 | if (this.Scoring == FeatureScoring.NotScored) 51 | return 0; 52 | 53 | if (this.Scoring == FeatureScoring.SinglePoint) 54 | return this.Features.Any(f => this[library, f].State == FeatureState.Success) ? 1 : 0; 55 | 56 | return this.Features.Count(f => this[library, f].State == FeatureState.Success); 57 | } 58 | 59 | public int MaxScore { 60 | get { 61 | if (this.Scoring == FeatureScoring.NotScored) 62 | return 0; 63 | 64 | if (this.Scoring == FeatureScoring.SinglePoint) 65 | return 1; 66 | 67 | return this.Features.Count; 68 | } 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /#framework/Shared/SkipException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.Serialization; 5 | 6 | namespace FeatureTests.Shared { 7 | [Serializable] 8 | public class SkipException : Exception { 9 | public SkipException() {} 10 | public SkipException(string message) : base(message) {} 11 | public SkipException(string message, Exception inner) : base(message, inner) {} 12 | protected SkipException(SerializationInfo info, StreamingContext context) : base(info, context) {} 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /#framework/Shared/SpecialCaseAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace FeatureTests.Shared { 6 | [AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = true)] 7 | public class SpecialCaseAttribute : Attribute { 8 | public Type AdapterType { get; private set; } 9 | public string Comment { get; private set; } 10 | 11 | /// 12 | /// Defines whether to skip tests for this . 13 | /// 14 | public bool Skip { get; set; } 15 | 16 | public SpecialCaseAttribute(Type adapterType, string comment) { 17 | this.AdapterType = adapterType; 18 | this.Comment = comment; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /#framework/Shared/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /#generated/FeatureTests.css: -------------------------------------------------------------------------------- 1 | @import url(http://fonts.googleapis.com/css?family=Open+Sans); 2 | 3 | @media only screen and (max-width: 760px) { 4 | body { 5 | background-color: white; 6 | } 7 | 8 | .container { 9 | padding: 0; 10 | min-width: 0; 11 | } 12 | } 13 | 14 | html { 15 | margin: 0; 16 | padding: 0; 17 | } 18 | 19 | body { 20 | font-family: "Segoe UI", "Open Sans", sans-serif; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | h1 { 26 | text-align: center; 27 | } 28 | 29 | .container { 30 | max-width: 1200px; 31 | margin: 0 auto; 32 | } 33 | 34 | .content { 35 | margin-left: 220px; 36 | } 37 | 38 | h1, h2 { 39 | font-weight: normal; 40 | } 41 | 42 | h1 { 43 | font-size: 160%; 44 | padding-bottom: 1rem; 45 | } 46 | 47 | h2 { 48 | font-size: 125%; 49 | 50 | margin: 0; 51 | margin-bottom: 0.1rem; 52 | margin-top: 2.5rem; 53 | 54 | padding-bottom: 0.2rem; 55 | border-bottom: 1px solid #eee; 56 | } 57 | 58 | section:first-of-type h2 { 59 | margin-top: 0; 60 | } 61 | 62 | .section-description { 63 | margin-bottom: 0.3rem; 64 | } 65 | 66 | .section-description, .feature-descriptions, .comments { 67 | max-width: 60rem; 68 | } 69 | 70 | dt, dd { 71 | margin: 0; 72 | padding: 0; 73 | } 74 | 75 | dt { 76 | font-size: 105%; 77 | } 78 | 79 | dd { 80 | margin-left: 0.8rem; 81 | margin-top: 0.2rem; 82 | } 83 | 84 | dd p { 85 | margin-top: 0; 86 | } 87 | 88 | dd + dt { 89 | margin-top: 0.5rem; 90 | } 91 | 92 | table { 93 | margin: 0; 94 | border: 0; 95 | border-collapse: collapse; 96 | 97 | margin-top: 1rem; 98 | 99 | font-size: 90%; 100 | } 101 | 102 | th { 103 | font-weight: normal; 104 | max-width: 10em; 105 | background-color: #ccc; 106 | 107 | overflow: hidden; 108 | text-overflow: ellipsis; 109 | } 110 | 111 | th, td { 112 | padding: 0.2rem; 113 | text-align: center; 114 | } 115 | 116 | td { 117 | background-color: #eee; 118 | } 119 | 120 | td.framework-name, th:first-child { 121 | text-align: left; 122 | padding-left: 0.6rem; 123 | padding-right: 0.6rem; 124 | } 125 | 126 | td.number, td.date { 127 | text-align: right; 128 | padding-left: 0.6rem; 129 | padding-right: 0.6rem; 130 | } 131 | 132 | td.success { 133 | background-color: #c6efce; 134 | color: #006100; 135 | } 136 | 137 | td.failure { 138 | background-color: #ffc7ce; 139 | color: #9c0006; 140 | } 141 | 142 | td.skipped { 143 | background-color: #eee; 144 | color: #c0c0c0; 145 | } 146 | 147 | td.concern { 148 | background-color: #ffeba5; 149 | color: #9c6500; 150 | } 151 | 152 | td > a:only-child { 153 | display: block; 154 | width: 100%; 155 | height: 100%; 156 | } 157 | 158 | a { 159 | text-decoration: none; 160 | } 161 | 162 | a:hover { 163 | text-decoration: underline; 164 | } 165 | 166 | td.success a, td.failure a, td.concern a, td.skipped a { 167 | color: inherit; 168 | } 169 | 170 | .toc-wrapper { 171 | position: absolute; 172 | } 173 | 174 | .toc { 175 | position: absolute; 176 | width: 200px; 177 | border: 1px solid #ccc; 178 | } 179 | 180 | .toc ul { 181 | margin: 0; 182 | padding: 0; 183 | list-style: none; 184 | } 185 | 186 | .toc a { 187 | color: #000; 188 | text-decoration: none; 189 | display: block; 190 | padding: 5px 10px; 191 | } 192 | 193 | .toc .toc-h2 a { 194 | padding-left: 10px; 195 | } 196 | 197 | .toc .toc-h3 a { 198 | padding-left: 20px; 199 | } 200 | 201 | .toc .toc-active { 202 | background-color: #eee; 203 | } -------------------------------------------------------------------------------- /#generated/js/jquery.sticky.js: -------------------------------------------------------------------------------- 1 | (function(e){var t={topSpacing:0,bottomSpacing:0,className:"is-sticky",wrapperClassName:"sticky-wrapper",center:false,getWidthFrom:""},n=e(window),r=e(document),i=[],s=n.height(),o=function(){var t=n.scrollTop(),o=r.height(),u=o-s,a=t>u?u-t:0;for(var f=0;f").attr("id",n+"-sticky-wrapper").addClass(r.wrapperClassName);t.wrapAll(s);if(r.center){t.parent().css({width:t.outerWidth(),marginLeft:"auto",marginRight:"auto"})}if(t.css("float")=="right"){t.css({"float":"none"}).parent().css({"float":"right"})}var o=t.parent();o.css("height",t.outerHeight());i.push({topSpacing:r.topSpacing,bottomSpacing:r.bottomSpacing,stickyElement:t,currentTop:null,stickyWrapper:o,className:r.className,getWidthFrom:r.getWidthFrom})})},update:o};if(window.addEventListener){window.addEventListener("scroll",o,false);window.addEventListener("resize",u,false)}else if(window.attachEvent){window.attachEvent("onscroll",o);window.attachEvent("onresize",u)}e.fn.sticky=function(t){if(a[t]){return a[t].apply(this,Array.prototype.slice.call(arguments,1))}else if(typeof t==="object"||!t){return a.init.apply(this,arguments)}else{e.error("Method "+t+" does not exist on jQuery.sticky")}};e(function(){setTimeout(o,0)})})(jQuery) -------------------------------------------------------------------------------- /#generated/js/jquery.toc.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * toc - jQuery Table of Contents Plugin 3 | * v0.1.2 4 | * http://projects.jga.me/toc/ 5 | * copyright Greg Allen 2013 6 | * MIT License 7 | */ 8 | (function(t){t.fn.toc=function(e){var n,i=this,r=t.extend({},jQuery.fn.toc.defaults,e),o=t(r.container),a=t(r.selectors,o),l=[],h=r.prefix+"-active",s=function(e){if(r.smoothScrolling){e.preventDefault();var n=t(e.target).attr("href"),o=t(n);t("body,html").animate({scrollTop:o.offset().top},400,"swing",function(){location.hash=n})}t("li",i).removeClass(h),t(e.target).parent().addClass(h)},c=function(){n&&clearTimeout(n),n=setTimeout(function(){for(var e,n=t(window).scrollTop(),o=0,a=l.length;a>o;o++)if(l[o]>=n){t("li",i).removeClass(h),e=t("li:eq("+(o-1)+")",i).addClass(h),r.onHighlight(e);break}},50)};return r.highlightOnScroll&&(t(window).bind("scroll",c),c()),this.each(function(){var e=t(this),n=t("