diff --git a/Generator/BatchGenerator.cs b/Generator/BatchGenerator.cs new file mode 100644 index 000000000..f08dcfcc0 --- /dev/null +++ b/Generator/BatchGenerator.cs @@ -0,0 +1,97 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using TechTalk.SpecFlow.Generator.Configuration; + +namespace TechTalk.SpecFlow.Generator +{ + public class BatchGenerator + { + private readonly TextWriter traceWriter; + private readonly bool verboseOutput; + + public BatchGenerator(TextWriter traceWriter, bool verboseOutput) + { + this.traceWriter = traceWriter; + this.verboseOutput = verboseOutput; + } + + public void ProcessProject(SpecFlowProject specFlowProject, bool forceGenerate) + { + if (verboseOutput) + traceWriter.WriteLine("Processing project: " + specFlowProject); + + SpecFlowGenerator generator = new SpecFlowGenerator(specFlowProject); + + foreach (var featureFile in specFlowProject.FeatureFiles) + { + string featureFileFullPath = featureFile.GetFullPath(specFlowProject); + + string codeFileFullPath = featureFileFullPath + ".cs"; + + bool needsProcessing = true; + + if (!forceGenerate && IsUpToDate(generator, featureFileFullPath, codeFileFullPath)) + { + needsProcessing = false; + } + + if (verboseOutput || needsProcessing) + { + traceWriter.WriteLine("Processing file: {0}", featureFileFullPath); + if (!needsProcessing) + traceWriter.WriteLine(" up-to-date"); + } + + if (needsProcessing) + { + GenerateFile(generator, featureFile, codeFileFullPath); + } + } + } + + protected virtual void GenerateFile(SpecFlowGenerator generator, SpecFlowFeatureFile featureFile, string codeFileFullPath) + { + using (var writer = GetWriter(codeFileFullPath)) + { + generator.GenerateCSharpTestFile(featureFile, writer); + } + } + + protected virtual StreamWriter GetWriter(string codeFileFullPath) + { + return new StreamWriter(codeFileFullPath, false, Encoding.UTF8); + } + + private static bool IsUpToDate(SpecFlowGenerator generator, string featureFileFullPath, string codeFileFullPath) + { + // check existance of the target file + if (!File.Exists(codeFileFullPath)) + return false; + + // check modification time of the target file + try + { + var featureFileModificationTime = File.GetLastWriteTime(featureFileFullPath); + var codeFileModificationTime = File.GetLastWriteTime(codeFileFullPath); + + if (featureFileModificationTime > codeFileModificationTime) + return false; + } + catch (Exception ex) + { + Debug.WriteLine(ex); + return false; + } + + // check tools version + var codeFileVersion = generator.GetGeneratedFileSpecFlowVersion(codeFileFullPath); + if (codeFileVersion == null || codeFileVersion != generator.GetCurrentSpecFlowVersion()) + return false; + + return true; + } + } +} \ No newline at end of file diff --git a/Parser/Configuration/GeneratorConfiguration.cs b/Generator/Configuration/GeneratorConfiguration.cs similarity index 80% rename from Parser/Configuration/GeneratorConfiguration.cs rename to Generator/Configuration/GeneratorConfiguration.cs index 9418c6cc2..bb716e449 100644 --- a/Parser/Configuration/GeneratorConfiguration.cs +++ b/Generator/Configuration/GeneratorConfiguration.cs @@ -2,9 +2,9 @@ using System.Globalization; using System.Linq; using TechTalk.SpecFlow.Configuration; -using TechTalk.SpecFlow.Parser.UnitTestProvider; +using TechTalk.SpecFlow.Generator.UnitTestProvider; -namespace TechTalk.SpecFlow.Parser.Configuration +namespace TechTalk.SpecFlow.Generator.Configuration { public class GeneratorConfiguration { @@ -22,8 +22,8 @@ public GeneratorConfiguration() { FeatureLanguage = CultureInfo.GetCultureInfo(ConfigDefaults.FeatureLanguage); ToolLanguage = string.IsNullOrEmpty(ConfigDefaults.ToolLanguage) ? - FeatureLanguage : - CultureInfo.GetCultureInfo(ConfigDefaults.ToolLanguage); + FeatureLanguage : + CultureInfo.GetCultureInfo(ConfigDefaults.ToolLanguage); SetUnitTestDefaultsByName(ConfigDefaults.UnitTestProviderName); @@ -38,8 +38,8 @@ internal void UpdateFromConfigFile(ConfigurationSectionHandler configSection) { FeatureLanguage = CultureInfo.GetCultureInfo(configSection.Language.Feature); ToolLanguage = string.IsNullOrEmpty(configSection.Language.Tool) ? - FeatureLanguage : - CultureInfo.GetCultureInfo(configSection.Language.Tool); + FeatureLanguage : + CultureInfo.GetCultureInfo(configSection.Language.Tool); } if (configSection.UnitTestProvider != null) @@ -81,4 +81,4 @@ private void SetUnitTestDefaultsByName(string name) } } -} +} \ No newline at end of file diff --git a/Parser/Configuration/GeneratorConfigurationReader.cs b/Generator/Configuration/GeneratorConfigurationReader.cs similarity index 96% rename from Parser/Configuration/GeneratorConfigurationReader.cs rename to Generator/Configuration/GeneratorConfigurationReader.cs index 9fed1963c..c426d3ebc 100644 --- a/Parser/Configuration/GeneratorConfigurationReader.cs +++ b/Generator/Configuration/GeneratorConfigurationReader.cs @@ -4,7 +4,7 @@ using System.Xml; using TechTalk.SpecFlow.Configuration; -namespace TechTalk.SpecFlow.Parser.Configuration +namespace TechTalk.SpecFlow.Generator.Configuration { public static class GeneratorConfigurationReader { diff --git a/Generator/Configuration/MsBuildProjectReader.cs b/Generator/Configuration/MsBuildProjectReader.cs new file mode 100644 index 000000000..cd4093810 --- /dev/null +++ b/Generator/Configuration/MsBuildProjectReader.cs @@ -0,0 +1,52 @@ +using System; +using System.IO; +using System.Linq; +using Microsoft.Build.BuildEngine; +using TechTalk.SpecFlow.Generator.Configuration; + +namespace TechTalk.SpecFlow.Generator.Configuration +{ + public static class MsBuildProjectReader + { + public static SpecFlowProject LoadSpecFlowProjectFromMsBuild(string projectFile) + { + projectFile = Path.GetFullPath(projectFile); + + Project project = Engine.GlobalEngine.GetLoadedProject(projectFile); + if (project == null) + { + project = new Project(); + project.Load(projectFile, ProjectLoadSettings.IgnoreMissingImports); + } + + string projectFolder = Path.GetDirectoryName(projectFile); + + SpecFlowProject specFlowProject = new SpecFlowProject(); + specFlowProject.ProjectFolder = projectFolder; + specFlowProject.ProjectName = Path.GetFileNameWithoutExtension(projectFile); + specFlowProject.AssemblyName = project.GetEvaluatedProperty("AssemblyName"); + specFlowProject.DefaultNamespace = project.GetEvaluatedProperty("RootNamespace"); + + var items = project.GetEvaluatedItemsByName("None").Cast() + .Concat(project.GetEvaluatedItemsByName("Content").Cast()); + foreach (BuildItem item in items) + { + var extension = Path.GetExtension(item.FinalItemSpec); + if (extension.Equals(".feature", StringComparison.InvariantCultureIgnoreCase)) + { + var featureFile = new SpecFlowFeatureFile(item.FinalItemSpec); + var ns = item.GetEvaluatedMetadata("CustomToolNamespace"); + if (!String.IsNullOrEmpty(ns)) + featureFile.CustomNamespace = ns; + specFlowProject.FeatureFiles.Add(featureFile); + } + + if (Path.GetFileName(item.FinalItemSpec).Equals("app.config", StringComparison.InvariantCultureIgnoreCase)) + { + GeneratorConfigurationReader.UpdateConfigFromFile(specFlowProject.GeneratorConfiguration, Path.Combine(projectFolder, item.FinalItemSpec)); + } + } + return specFlowProject; + } + } +} \ No newline at end of file diff --git a/Parser/Configuration/SpecFlowProject.cs b/Generator/Configuration/SpecFlowProject.cs similarity index 65% rename from Parser/Configuration/SpecFlowProject.cs rename to Generator/Configuration/SpecFlowProject.cs index a572761ce..40408e8d8 100644 --- a/Parser/Configuration/SpecFlowProject.cs +++ b/Generator/Configuration/SpecFlowProject.cs @@ -1,7 +1,8 @@ +using System; using System.Collections.Generic; using System.IO; -namespace TechTalk.SpecFlow.Parser.Configuration +namespace TechTalk.SpecFlow.Generator.Configuration { public class SpecFlowFeatureFile { @@ -32,5 +33,16 @@ public SpecFlowProject() FeatureFiles = new List(); GeneratorConfiguration = new GeneratorConfiguration(); // load defaults } + + public SpecFlowFeatureFile GetOrCreateFeatureFile(string featureFileName) + { + featureFileName = Path.GetFullPath(Path.Combine(ProjectFolder, featureFileName)); + var result = FeatureFiles.Find(ff => ff.GetFullPath(this).Equals(featureFileName, StringComparison.InvariantCultureIgnoreCase)); + if (result == null) + { + result = new SpecFlowFeatureFile(featureFileName); //TODO: make it project relative + } + return result; + } } } \ No newline at end of file diff --git a/Generator/Properties/AssemblyInfo.cs b/Generator/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..428fe648d --- /dev/null +++ b/Generator/Properties/AssemblyInfo.cs @@ -0,0 +1,23 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TechTalk.SpecFlow.Generator")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TechTalk.SpecFlow.Generator")] +[assembly: AssemblyCopyright("Copyright © 2009")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("743e1609-8937-4775-9896-6e8757c6d98d")] diff --git a/Generator/SpecFlowGenerator.cs b/Generator/SpecFlowGenerator.cs new file mode 100644 index 000000000..302d517fd --- /dev/null +++ b/Generator/SpecFlowGenerator.cs @@ -0,0 +1,169 @@ +using System; +using System.CodeDom; +using System.CodeDom.Compiler; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using Microsoft.CSharp; +using TechTalk.SpecFlow.Configuration; +using TechTalk.SpecFlow.Generator.Configuration; +using TechTalk.SpecFlow.Generator.UnitTestProvider; +using TechTalk.SpecFlow.Parser; +using TechTalk.SpecFlow.Parser.SyntaxElements; + +namespace TechTalk.SpecFlow.Generator +{ + public class SpecFlowGenerator + { + private readonly SpecFlowProject project; + + public SpecFlowGenerator(SpecFlowProject project) + { + this.project = project; + } + + public void GenerateCSharpTestFile(SpecFlowFeatureFile featureFile, TextWriter outputWriter) + { + var codeProvider = new CSharpCodeProvider(); + GenerateTestFile(featureFile, codeProvider, outputWriter); + } + + private void GenerateTestFile(SpecFlowFeatureFile featureFile, CodeDomProvider codeProvider, TextWriter outputWriter) + { + using(var reader = new StreamReader(featureFile.GetFullPath(project))) + { + GenerateTestFile(featureFile, codeProvider, reader, outputWriter); + } + } + + public void GenerateTestFile(SpecFlowFeatureFile featureFile, CodeDomProvider codeProvider, TextReader inputReader, TextWriter outputWriter) + { + var codeNamespace = GenerateTestFileCode(featureFile, inputReader); + var options = new CodeGeneratorOptions + { + BracingStyle = "C" + }; + + AddSpecFlowHeader(codeProvider, outputWriter); + codeProvider.GenerateCodeFromNamespace(codeNamespace, outputWriter, options); + outputWriter.Flush(); + } + + public CodeNamespace GenerateTestFileCode(SpecFlowFeatureFile featureFile, TextReader inputReader) + { + string targetNamespace = GetTargetNamespace(featureFile); + + SpecFlowLangParser parser = new SpecFlowLangParser(project.GeneratorConfiguration.FeatureLanguage); + Feature feature = parser.Parse(inputReader, featureFile.GetFullPath(project)); + + IUnitTestGeneratorProvider generatorProvider = ConfigurationServices.CreateInstance(project.GeneratorConfiguration.GeneratorUnitTestProviderType); + + SpecFlowUnitTestConverter testConverter = new SpecFlowUnitTestConverter(generatorProvider); + + var codeNamespace = testConverter.GenerateUnitTestFixture(feature, null, targetNamespace); + return codeNamespace; + } + + private string GetTargetNamespace(SpecFlowFeatureFile featureFile) + { + if (!string.IsNullOrEmpty(featureFile.CustomNamespace)) + return featureFile.CustomNamespace; + + if (string.IsNullOrEmpty(project.DefaultNamespace)) + return null; + + string targetNamespace = project.DefaultNamespace; + string projectFolder = project.ProjectFolder; + string sourceFileFolder = Path.GetDirectoryName(featureFile.GetFullPath(project)); + if (sourceFileFolder.StartsWith(sourceFileFolder, StringComparison.InvariantCultureIgnoreCase)) + { + string extraFolders = sourceFileFolder.Substring(projectFolder.Length); + if (extraFolders.Length > 0) + { + string[] parts = extraFolders.TrimStart('\\').Split('\\'); + targetNamespace += "." + string.Join(".", + parts.Select(p => p.ToIdentifier()).ToArray()); + } + } + return targetNamespace; + } + + private void AddSpecFlowHeader(CodeDomProvider codeProvider, TextWriter outputWriter) + { + var specFlowHeaderTemplate = @"------------------------------------------------------------------------------ + + This code was generated by SpecFlow (http://www.specflow.org/). + SpecFlow Version:{0} + Runtime Version:{1} + + Changes to this file may cause incorrect behavior and will be lost if + the code is regenerated. + +------------------------------------------------------------------------------"; + + var headerReader = new StringReader(string.Format(specFlowHeaderTemplate, + GetCurrentSpecFlowVersion(), + Environment.Version + )); + + string line; + while ((line = headerReader.ReadLine()) != null) + { + codeProvider.GenerateCodeFromStatement(new CodeCommentStatement(line), outputWriter, null); + } + } + + public Version GetCurrentSpecFlowVersion() + { + return Assembly.GetExecutingAssembly().GetName().Version; + } + + static private readonly Regex versionRe = new Regex(@"SpecFlow Version:\s*(?\d+\.\d+\.\d+\.\d+)"); + + public Version GetGeneratedFileSpecFlowVersion(string filePath) + { + try + { + if (!File.Exists(filePath)) + return null; + + using (var reader = new StreamReader(filePath)) + { + return GetGeneratedFileSpecFlowVersion(reader); + } + } + catch (Exception ex) + { + Debug.WriteLine(ex); + return null; + } + } + + private Version GetGeneratedFileSpecFlowVersion(StreamReader reader) + { + try + { + const int maxLinesToScan = 10; + + int lineNo = 0; + string line; + while ((line = reader.ReadLine()) != null && lineNo < maxLinesToScan) + { + var match = versionRe.Match(line); + if (match.Success) + return new Version(match.Groups["ver"].Value); + + lineNo++; + } + return null; + } + catch (Exception ex) + { + Debug.WriteLine(ex); + return null; + } + } + } +} \ No newline at end of file diff --git a/Parser/SpecFlowUnitTestConverter.cs b/Generator/SpecFlowUnitTestConverter.cs similarity index 91% rename from Parser/SpecFlowUnitTestConverter.cs rename to Generator/SpecFlowUnitTestConverter.cs index 387406b0c..e25dd186a 100644 --- a/Parser/SpecFlowUnitTestConverter.cs +++ b/Generator/SpecFlowUnitTestConverter.cs @@ -5,12 +5,11 @@ using System.IO; using System.Linq; using System.Reflection; -using System.Text; using System.Text.RegularExpressions; +using TechTalk.SpecFlow.Generator.UnitTestProvider; using TechTalk.SpecFlow.Parser.SyntaxElements; -using TechTalk.SpecFlow.Parser.UnitTestProvider; -namespace TechTalk.SpecFlow.Parser +namespace TechTalk.SpecFlow.Generator { public class SpecFlowUnitTestConverter { @@ -40,15 +39,13 @@ public SpecFlowUnitTestConverter(IUnitTestGeneratorProvider testGeneratorProvide this.allowDebugGeneratedFiles = allowDebugGeneratedFiles; } - public CodeCompileUnit GenerateUnitTestFixture(Feature feature, string testClassName, string targetNamespace) + public CodeNamespace GenerateUnitTestFixture(Feature feature, string testClassName, string targetNamespace) { targetNamespace = targetNamespace ?? DEFAULT_NAMESPACE; testClassName = testClassName ?? string.Format(FIXTURE_FORMAT, feature.Title.ToIdentifier()); - CodeCompileUnit codeCompileUnit = new CodeCompileUnit(); CodeNamespace codeNamespace = new CodeNamespace(targetNamespace); - codeCompileUnit.Namespaces.Add(codeNamespace); codeNamespace.Imports.Add(new CodeNamespaceImport(SPECFLOW_NAMESPACE)); @@ -84,8 +81,7 @@ public CodeCompileUnit GenerateUnitTestFixture(Feature feature, string testClass else GenerateTest(testType, testSetup, scenario, feature); } - - return codeCompileUnit; + return codeNamespace; } private void DeclareTestRunnerMember(CodeTypeDeclaration testType) @@ -180,12 +176,12 @@ private CodeMemberMethod GenerateTestFixtureTearDown(CodeTypeDeclaration testTyp testGeneratorProvider.SetTestFixtureTearDown(tearDownMethod); var testRunnerField = GetTestRunnerExpression(); -// testRunner.OnFeatureEnd(); + // testRunner.OnFeatureEnd(); tearDownMethod.Statements.Add( new CodeMethodInvokeExpression( testRunnerField, "OnFeatureEnd")); -// testRunner = null; + // testRunner = null; tearDownMethod.Statements.Add( new CodeAssignStatement( testRunnerField, @@ -298,14 +294,14 @@ private void GenerateScenarioOutlineTest(CodeTypeDeclaration testType, CodeMembe foreach (var exampleSet in scenarioOutline.Examples.ExampleSets) { string exampleSetTitle = exampleSet.Title == null ? string.Format("Scenarios{0}", exampleSetIndex + 1) : - exampleSet.Title.ToIdentifier(); + exampleSet.Title.ToIdentifier(); bool useFirstColumnAsName = CanUseFirstColumnAsName(exampleSet.Table); for (int rowIndex = 0; rowIndex < exampleSet.Table.Body.Length; rowIndex++) { string variantName = useFirstColumnAsName ? exampleSet.Table.Body[rowIndex].Cells[0].Value.ToIdentifierPart() : - string.Format("Variant{0}", rowIndex); + string.Format("Variant{0}", rowIndex); GenerateScenarioOutlineTestVariant(testType, scenarioOutline, testMethodName, paramToIdentifier, exampleSetTitle, exampleSet.Table.Body[rowIndex], variantName); } exampleSetIndex++; @@ -340,8 +336,8 @@ private void GenerateScenarioOutlineTestVariant(CodeTypeDeclaration testType, Sc { CodeMemberMethod testMethod = GetTestMethodDeclaration(testType, scenarioOutline); testMethod.Name = string.IsNullOrEmpty(exampleSetTitle) ? - string.Format("{0}_{1}", testMethod.Name, variantName) : - string.Format("{0}_{1}_{2}", testMethod.Name, exampleSetTitle, variantName); + string.Format("{0}_{1}", testMethod.Name, variantName) : + string.Format("{0}_{1}_{2}", testMethod.Name, exampleSetTitle, variantName); //call test implementation with the params List argumentExpressions = new List(); @@ -426,19 +422,19 @@ private CodeExpression GetSubstitutedString(string text, ParameterSubstitution p List arguments = new List(); formatText = paramRe.Replace(formatText, match => - { - string param = match.Groups["param"].Value; - string id; - if (!paramToIdentifier.TryGetIdentifier(param, out id)) - return match.Value; - int argIndex = arguments.IndexOf(id); - if (argIndex < 0) - { - argIndex = arguments.Count; - arguments.Add(id); - } - return "{" + argIndex + "}"; - }); + { + string param = match.Groups["param"].Value; + string id; + if (!paramToIdentifier.TryGetIdentifier(param, out id)) + return match.Value; + int argIndex = arguments.IndexOf(id); + if (argIndex < 0) + { + argIndex = arguments.Count; + arguments.Add(id); + } + return "{" + argIndex + "}"; + }); if (arguments.Count == 0) return new CodePrimitiveExpression(text); @@ -491,9 +487,9 @@ private CodeExpression GetTableArgExpression(Table tableArg, CodeStatementCollec var tableVar = new CodeVariableReferenceExpression("table" + tableCounter); statements.Add( new CodeVariableDeclarationStatement(TABLE_TYPE, tableVar.VariableName, - new CodeObjectCreateExpression( - TABLE_TYPE, - GetStringArrayExpression(tableArg.Header.Cells.Select(c => c.Value), paramToIdentifier)))); + new CodeObjectCreateExpression( + TABLE_TYPE, + GetStringArrayExpression(tableArg.Header.Cells.Select(c => c.Value), paramToIdentifier)))); foreach (var row in tableArg.Body) { diff --git a/Generator/TechTalk.SpecFlow.Generator.csproj b/Generator/TechTalk.SpecFlow.Generator.csproj new file mode 100644 index 000000000..0c9364756 --- /dev/null +++ b/Generator/TechTalk.SpecFlow.Generator.csproj @@ -0,0 +1,87 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {453D8014-B6CD-4E86-80A8-D59F59092334} + Library + Properties + TechTalk.SpecFlow.Generator + TechTalk.SpecFlow.Generator + v3.5 + 512 + true + ..\specflow.snk + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + 3.5 + + + 3.5 + + + 3.5 + + + + + + + Configuration\ConfigurationSectionHandler.cs + + + StringExtensions.cs + + + VersionInfo.cs + + + + + + + + + + + + + + + + {7CCEF6D6-FC17-422E-9BED-EDD752B6496F} + TechTalk.SpecFlow.Parser + + + + + \ No newline at end of file diff --git a/Parser/UnitTestProvider/IUnitTestGeneratorProvider.cs b/Generator/UnitTestProvider/IUnitTestGeneratorProvider.cs similarity index 93% rename from Parser/UnitTestProvider/IUnitTestGeneratorProvider.cs rename to Generator/UnitTestProvider/IUnitTestGeneratorProvider.cs index 1049b8ebd..303ae4e70 100644 --- a/Parser/UnitTestProvider/IUnitTestGeneratorProvider.cs +++ b/Generator/UnitTestProvider/IUnitTestGeneratorProvider.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; -namespace TechTalk.SpecFlow.Parser.UnitTestProvider +namespace TechTalk.SpecFlow.Generator.UnitTestProvider { public interface IUnitTestGeneratorProvider { diff --git a/Parser/UnitTestProvider/MsTestGeneratorProvider.cs b/Generator/UnitTestProvider/MsTestGeneratorProvider.cs similarity index 98% rename from Parser/UnitTestProvider/MsTestGeneratorProvider.cs rename to Generator/UnitTestProvider/MsTestGeneratorProvider.cs index 7af191e1f..e50d07cdb 100644 --- a/Parser/UnitTestProvider/MsTestGeneratorProvider.cs +++ b/Generator/UnitTestProvider/MsTestGeneratorProvider.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; -namespace TechTalk.SpecFlow.Parser.UnitTestProvider +namespace TechTalk.SpecFlow.Generator.UnitTestProvider { public class MsTestGeneratorProvider : IUnitTestGeneratorProvider { @@ -124,4 +124,4 @@ public void SetIgnore(CodeTypeMember codeTypeMember) new CodeTypeReference(IGNORE_ATTR))); } } -} +} \ No newline at end of file diff --git a/Parser/UnitTestProvider/NUnitTestConverter.cs b/Generator/UnitTestProvider/NUnitTestConverter.cs similarity index 98% rename from Parser/UnitTestProvider/NUnitTestConverter.cs rename to Generator/UnitTestProvider/NUnitTestConverter.cs index d5cc2021e..64f735ebc 100644 --- a/Parser/UnitTestProvider/NUnitTestConverter.cs +++ b/Generator/UnitTestProvider/NUnitTestConverter.cs @@ -2,9 +2,8 @@ using System.CodeDom; using System.Collections.Generic; using System.Linq; -using TechTalk.SpecFlow.Parser.UnitTestProvider; -namespace TechTalk.SpecFlow.Parser +namespace TechTalk.SpecFlow.Generator.UnitTestProvider { public class NUnitTestConverter : IUnitTestGeneratorProvider { diff --git a/GeneratorTools/Generator.cs b/GeneratorTools/Generator.cs deleted file mode 100644 index f1a027fd4..000000000 --- a/GeneratorTools/Generator.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.CodeDom; -using System.CodeDom.Compiler; -using System.IO; -using System.Text; -using Microsoft.CSharp; -using NConsoler; -using TechTalk.SpecFlow.Parser; -using TechTalk.SpecFlow.Parser.Configuration; -using TechTalk.SpecFlow.Reporting; - -namespace TechTalk.SpecFlow.GeneratorTools -{ - class Program - { - static void Main(string[] args) - { - Consolery.Run(typeof(Program), args); - return; - } - - [Action("Generate tests from all feature files")] - public static void Generate( - [Required] string projectFile - ) - { - SpecFlowProject specFlowProject = MsBuildProjectReader.LoadSpecFlowProjectFromMsBuild(projectFile); - - ProcessProject(specFlowProject); - } - - private static void ProcessProject(SpecFlowProject specFlowProject) - { - Console.WriteLine("Processing project: " + specFlowProject); - - foreach (var featureFile in specFlowProject.FeatureFiles) - { - string featureFileFullPath = featureFile.GetFullPath(specFlowProject); - - Console.WriteLine("Processing file: " + featureFileFullPath); - - SpecFlowGenerator generator = new SpecFlowGenerator(specFlowProject); - CodeCompileUnit compileUnit = generator.GenerateTestFileCode(featureFile); - - string codeFileFullPath = featureFileFullPath + ".cs"; - using (var writer = new StreamWriter(codeFileFullPath, false, Encoding.UTF8)) - { - CSharpCodeProvider codeProvider = new CSharpCodeProvider(); - CodeGeneratorOptions options = new CodeGeneratorOptions(); - options.BracingStyle = "C"; - codeProvider.GenerateCodeFromCompileUnit(compileUnit, writer, options); - } - } - } - } -} \ No newline at end of file diff --git a/GeneratorTools/NConsoler/NConsoler.cs b/GeneratorTools/NConsoler/NConsoler.cs deleted file mode 100644 index 375a5b581..000000000 --- a/GeneratorTools/NConsoler/NConsoler.cs +++ /dev/null @@ -1,925 +0,0 @@ -// -// NConsoler 0.9.3 -// http://nconsoler.csharpus.com -// - -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Diagnostics; - -namespace NConsoler -{ - /// - /// Entry point for NConsoler applications - /// - public sealed class Consolery - { - /// - /// Runs an appropriate Action method. - /// Uses the class this call lives in as target type and command line arguments from Environment - /// - public static void Run() - { - Type declaringType = new StackTrace().GetFrame(1).GetMethod().DeclaringType; - string[] args = new string[Environment.GetCommandLineArgs().Length - 1]; - new List(Environment.GetCommandLineArgs()).CopyTo(1, args, 0, Environment.GetCommandLineArgs().Length - 1); - Run(declaringType, args); - } - - /// - /// Runs an appropriate Action method - /// - /// Type where to search for Action methods - /// Arguments that will be converted to Action method arguments - public static void Run(Type targetType, string[] args) - { - Run(targetType, args, new ConsoleMessenger()); - } - - /// - /// Runs an appropriate Action method - /// - /// Type where to search for Action methods - /// Arguments that will be converted to Action method arguments - /// Uses for writing messages instead of Console class methods - public static void Run(Type targetType, string[] args, IMessenger messenger) - { - try - { - new Consolery(targetType, args, messenger).RunAction(); - } - catch (NConsolerException e) - { - messenger.Write(e.Message); - } - } - - /// - /// Validates specified type and throws NConsolerException if an error - /// - /// Type where to search for Action methods - public static void Validate(Type targetType) - { - new Consolery(targetType, new string[] {}, new ConsoleMessenger()).ValidateMetadata(); - } - - private readonly Type _targetType; - private readonly string[] _args; - private readonly List _actionMethods = new List(); - private readonly IMessenger _messenger; - - private Consolery(Type targetType, string[] args, IMessenger messenger) - { - #region Parameter Validation - if (targetType == null) - { - throw new ArgumentNullException("targetType"); - } - if (args == null) - { - throw new ArgumentNullException("args"); - } - if (messenger == null) - { - throw new ArgumentNullException("messenger"); - } - #endregion - _targetType = targetType; - _args = args; - _messenger = messenger; - MethodInfo[] methods = _targetType.GetMethods(BindingFlags.Public | BindingFlags.Static); - foreach (MethodInfo method in methods) - { - object[] attributes = method.GetCustomAttributes(false); - foreach (object attribute in attributes) - { - if (attribute is ActionAttribute) - { - _actionMethods.Add(method); - break; - } - } - } - } - - static object ConvertValue(string value, Type argumentType) - { - if (argumentType == typeof(int)) - { - try - { - return Convert.ToInt32(value); - } - catch (FormatException) - { - throw new NConsolerException("Could not convert \"{0}\" to integer", value); - } - catch (OverflowException) - { - throw new NConsolerException("Value \"{0}\" is too big or too small", value); - } - } - if (argumentType == typeof(string)) - { - return value; - } - if (argumentType == typeof(bool)) - { - try - { - return Convert.ToBoolean(value); - } - catch (FormatException) - { - throw new NConsolerException("Could not convert \"{0}\" to boolean", value); - } - } - if (argumentType == typeof(string[])) - { - return value.Split('+', ';'); - } - if (argumentType == typeof(int[])) - { - string[] values = value.Split('+'); - int[] valuesArray = new int[values.Length]; - for (int i = 0; i < values.Length; i++) - { - valuesArray[i] = (int)ConvertValue(values[i], typeof(int)); - } - return valuesArray; - } - if (argumentType == typeof(DateTime)) - { - return ConvertToDateTime(value); - } - throw new NConsolerException("Unknown type is used in your method {0}", argumentType.FullName); - } - - private static DateTime ConvertToDateTime(string parameter) - { - string[] parts = parameter.Split('-'); - if (parts.Length != 3) - { - throw new NConsolerException("Could not convert {0} to Date", parameter); - } - int day = (int)ConvertValue(parts[0], typeof(int)); - int month = (int)ConvertValue(parts[1], typeof(int)); - int year = (int)ConvertValue(parts[2], typeof(int)); - try - { - return new DateTime(year, month, day); - } - catch (ArgumentException) - { - throw new NConsolerException("Could not convert {0} to Date", parameter); - } - } - - private static bool CanBeConvertedToDate(string parameter) - { - try - { - ConvertToDateTime(parameter); - return true; - } - catch(NConsolerException) - { - return false; - } - } - - private bool SingleActionWithOnlyOptionalParametersSpecified() { - if (IsMulticommand) return false; - MethodInfo method = _actionMethods[0]; - return OnlyOptionalParametersSpecified(method); - } - - private static bool OnlyOptionalParametersSpecified(MethodBase method) - { - foreach (ParameterInfo parameter in method.GetParameters()) - { - if (IsRequired(parameter)) - { - return false; - } - } - return true; - } - - - private void RunAction() - { - ValidateMetadata(); - if (IsHelpRequested()) - { - PrintUsage(); - return; - } - - MethodInfo currentMethod = GetCurrentMethod(); - if (currentMethod == null) - { - PrintUsage(); - throw new NConsolerException("Unknown subcommand \"{0}\"", _args[0]); - } - ValidateInput(currentMethod); - InvokeMethod(currentMethod); - } - - private struct ParameterData - { - public readonly int position; - public readonly Type type; - - public ParameterData(int position, Type type) - { - this.position = position; - this.type = type; - } - } - - private static bool IsRequired(ICustomAttributeProvider info) - { - object[] attributes = info.GetCustomAttributes(typeof(ParameterAttribute), false); - return attributes.Length == 0 || attributes[0].GetType() == typeof(RequiredAttribute); - } - - private static bool IsOptional(ICustomAttributeProvider info) - { - return !IsRequired(info); - } - - private static OptionalAttribute GetOptional(ICustomAttributeProvider info) - { - object[] attributes = info.GetCustomAttributes(typeof(OptionalAttribute), false); - return (OptionalAttribute)attributes[0]; - } - - private bool IsMulticommand - { - get - { - return _actionMethods.Count > 1; - } - } - - private bool IsHelpRequested() - { - return (_args.Length == 0 && !SingleActionWithOnlyOptionalParametersSpecified()) - || (_args.Length > 0 && (_args[0] == "/?" - || _args[0] == "/help" - || _args[0] == "/h" - || _args[0] == "help")); - } - - private void InvokeMethod(MethodInfo method) - { - try - { - method.Invoke(null, BuildParameterArray(method)); - } - catch (TargetInvocationException e) - { - if (e.InnerException != null) - { - throw new NConsolerException(e.InnerException.Message, e); - } - throw; - } - } - - private object[] BuildParameterArray(MethodInfo method) - { - int argumentIndex = IsMulticommand ? 1 : 0; - List parameterValues = new List(); - Dictionary aliases = new Dictionary(); - foreach (ParameterInfo info in method.GetParameters()) - { - if (IsRequired(info)) - { - parameterValues.Add(ConvertValue(_args[argumentIndex], info.ParameterType)); - } - else - { - OptionalAttribute optional = GetOptional(info); - - foreach (string altName in optional.AltNames) - { - aliases.Add(altName.ToLower(), - new ParameterData(parameterValues.Count, info.ParameterType)); - } - aliases.Add(info.Name.ToLower(), - new ParameterData(parameterValues.Count, info.ParameterType)); - parameterValues.Add(optional.Default); - } - argumentIndex++; - } - foreach (string optionalParameter in OptionalParameters(method)) - { - string name = ParameterName(optionalParameter); - string value = ParameterValue(optionalParameter); - parameterValues[aliases[name].position] = ConvertValue(value, aliases[name].type); - } - return parameterValues.ToArray(); - } - - private IEnumerable OptionalParameters(MethodInfo method) - { - int firstOptionalParameterIndex = RequiredParameterCount(method); - if (IsMulticommand) - { - firstOptionalParameterIndex++; - } - for (int i = firstOptionalParameterIndex; i < _args.Length; i++) - { - yield return _args[i]; - } - } - - private static int RequiredParameterCount(MethodInfo method) - { - int requiredParameterCount = 0; - foreach (ParameterInfo parameter in method.GetParameters()) - { - if (IsRequired(parameter)) - { - requiredParameterCount++; - } - } - return requiredParameterCount; - } - - private MethodInfo GetCurrentMethod() - { - if (!IsMulticommand) - { - return _actionMethods[0]; - } - return GetMethodByName(_args[0].ToLower()); - } - - private MethodInfo GetMethodByName(string name) - { - foreach (MethodInfo method in _actionMethods) - { - if (method.Name.ToLower() == name) - { - return method; - } - } - return null; - } - - private void PrintUsage(MethodInfo method) - { - PrintMethodDescription(method); - Dictionary parameters = GetParametersDescriptions(method); - PrintUsageExample(method, parameters); - PrintParametersDescriptions(parameters); - } - - private void PrintUsageExample(MethodInfo method, IDictionary parameterList) - { - string subcommand = IsMulticommand ? method.Name.ToLower() + " " : String.Empty; - string parameters = String.Join(" ", new List(parameterList.Keys).ToArray()); - _messenger.Write("usage: " + ProgramName() + " " + subcommand + parameters); - } - - private void PrintMethodDescription(MethodInfo method) - { - string description = GetMethodDescription(method); - if (description == String.Empty) return; - _messenger.Write(description); - } - - private static string GetMethodDescription(MethodInfo method) - { - object[] attributes = method.GetCustomAttributes(true); - foreach (object attribute in attributes) - { - if (attribute is ActionAttribute) - { - return ((ActionAttribute)attribute).Description; - } - } - throw new NConsolerException("Method is not marked with an Action attribute"); - } - - private static Dictionary GetParametersDescriptions(MethodInfo method) - { - Dictionary parameters = new Dictionary(); - foreach (ParameterInfo parameter in method.GetParameters()) - { - object[] parameterAttributes = - parameter.GetCustomAttributes(typeof(ParameterAttribute), false); - if (parameterAttributes.Length > 0) - { - string name = GetDisplayName(parameter); - ParameterAttribute attribute = (ParameterAttribute)parameterAttributes[0]; - parameters.Add(name, attribute.Description); - } - else - { - parameters.Add(parameter.Name, String.Empty); - } - } - return parameters; - } - - private void PrintParametersDescriptions(IEnumerable> parameters) - { - int maxParameterNameLength = MaxKeyLength(parameters); - foreach (KeyValuePair pair in parameters) - { - if (pair.Value != String.Empty) - { - int difference = maxParameterNameLength - pair.Key.Length + 2; - _messenger.Write(" " + pair.Key + new String(' ', difference) + pair.Value); - } - } - } - - private static int MaxKeyLength(IEnumerable> parameters) - { - int maxLength = 0; - foreach (KeyValuePair pair in parameters) - { - if (pair.Key.Length > maxLength) - { - maxLength = pair.Key.Length; - } - } - return maxLength; - } - - private string ProgramName() - { - Assembly entryAssembly = Assembly.GetEntryAssembly(); - if (entryAssembly == null) - { - return _targetType.Name.ToLower(); - } - return new AssemblyName(entryAssembly.FullName).Name; - } - - private void PrintUsage() - { - if (IsMulticommand && !IsSubcommandHelpRequested()) - { - PrintGeneralMulticommandUsage(); - } - else if (IsMulticommand && IsSubcommandHelpRequested()) - { - PrintSubcommandUsage(); - } - else - { - PrintUsage(_actionMethods[0]); - } - } - - private void PrintSubcommandUsage() - { - MethodInfo method = GetMethodByName(_args[1].ToLower()); - if (method == null) - { - PrintGeneralMulticommandUsage(); - throw new NConsolerException("Unknown subcommand \"{0}\"", _args[0].ToLower()); - } - PrintUsage(method); - } - - private bool IsSubcommandHelpRequested() - { - return _args.Length > 0 - && _args[0].ToLower() == "help" - && _args.Length == 2; - } - - private void PrintGeneralMulticommandUsage() - { - _messenger.Write( - String.Format("usage: {0} [args]", ProgramName())); - _messenger.Write( - String.Format("Type '{0} help ' for help on a specific subcommand.", ProgramName())); - _messenger.Write(String.Empty); - _messenger.Write("Available subcommands:"); - foreach (MethodInfo method in _actionMethods) - { - _messenger.Write(method.Name.ToLower() + " " + GetMethodDescription(method)); - } - } - - private static string GetDisplayName(ParameterInfo parameter) - { - if (IsRequired(parameter)) - { - return parameter.Name; - } - OptionalAttribute optional = GetOptional(parameter); - string parameterName = - (optional.AltNames.Length > 0) ? optional.AltNames[0] : parameter.Name; - if (parameter.ParameterType != typeof(bool)) - { - parameterName += ":" + ValueDescription(parameter.ParameterType); - } - return "[/" + parameterName + "]"; - } - - private static string ValueDescription(Type type) - { - if (type == typeof(int)) - { - return "number"; - } - if (type == typeof(string)) - { - return "value"; - } - if (type == typeof(int[])) - { - return "number[+number]"; - } - if (type == typeof(string[])) - { - return "value[+value]"; - } - if (type == typeof(DateTime)) - { - return "dd-mm-yyyy"; - } - throw new ArgumentOutOfRangeException(String.Format("Type {0} is unknown", type.Name)); - } - - #region Validation - - private void ValidateInput(MethodInfo method) - { - CheckAllRequiredParametersAreSet(method); - CheckOptionalParametersAreNotDuplicated(method); - CheckUnknownParametersAreNotPassed(method); - } - - private void CheckAllRequiredParametersAreSet(MethodInfo method) - { - int minimumArgsLengh = RequiredParameterCount(method); - if (IsMulticommand) - { - minimumArgsLengh++; - } - if (_args.Length < minimumArgsLengh) - { - throw new NConsolerException("Not all required parameters are set"); - } - } - - private static string ParameterName(string parameter) - { - if (parameter.StartsWith("/-")) - { - return parameter.Substring(2).ToLower(); - } - if (parameter.Contains(":")) - { - return parameter.Substring(1, parameter.IndexOf(":") - 1).ToLower(); - } - return parameter.Substring(1).ToLower(); - } - - private static string ParameterValue(string parameter) - { - if (parameter.StartsWith("/-")) - { - return "false"; - } - if (parameter.Contains(":")) - { - return parameter.Substring(parameter.IndexOf(":") + 1); - } - return "true"; - } - - private void CheckOptionalParametersAreNotDuplicated(MethodInfo method) - { - List passedParameters = new List(); - foreach (string optionalParameter in OptionalParameters(method)) - { - if (!optionalParameter.StartsWith("/")) - { - throw new NConsolerException("Unknown parameter {0}", optionalParameter); - } - string name = ParameterName(optionalParameter); - if (passedParameters.Contains(name)) - { - throw new NConsolerException("Parameter with name {0} passed two times", name); - } - passedParameters.Add(name); - } - } - - private void CheckUnknownParametersAreNotPassed(MethodInfo method) - { - List parameterNames = new List(); - foreach (ParameterInfo parameter in method.GetParameters()) - { - if (IsRequired(parameter)) - { - continue; - } - parameterNames.Add(parameter.Name.ToLower()); - OptionalAttribute optional = GetOptional(parameter); - foreach (string altName in optional.AltNames) - { - parameterNames.Add(altName.ToLower()); - } - } - foreach (string optionalParameter in OptionalParameters(method)) - { - string name = ParameterName(optionalParameter); - if (!parameterNames.Contains(name.ToLower())) - { - throw new NConsolerException("Unknown parameter name {0}", optionalParameter); - } - } - } - - private void ValidateMetadata() - { - CheckAnyActionMethodExists(); - IfActionMethodIsSingleCheckMethodHasParameters(); - foreach (MethodInfo method in _actionMethods) - { - CheckActionMethodNamesAreNotReserved(); - CheckRequiredAndOptionalAreNotAppliedAtTheSameTime(method); - CheckOptionalParametersAreAfterRequiredOnes(method); - CheckOptionalParametersDefaultValuesAreAssignableToRealParameterTypes(method); - CheckOptionalParametersAltNamesAreNotDuplicated(method); - } - } - - private void CheckActionMethodNamesAreNotReserved() - { - foreach (MethodInfo method in _actionMethods) - { - if (method.Name.ToLower() == "help") - { - throw new NConsolerException("Method name \"{0}\" is reserved. Please, choose another name", method.Name); - } - } - } - - private void CheckAnyActionMethodExists() - { - if (_actionMethods.Count == 0) - { - throw new NConsolerException("Can not find any public static method marked with [Action] attribute in type \"{0}\"", _targetType.Name); - } - } - - private void IfActionMethodIsSingleCheckMethodHasParameters() - { - if (_actionMethods.Count == 1 && _actionMethods[0].GetParameters().Length == 0) - { - throw new NConsolerException("[Action] attribute applied once to the method \"{0}\" without parameters. In this case NConsoler should not be used", _actionMethods[0].Name); - } - } - - private static void CheckRequiredAndOptionalAreNotAppliedAtTheSameTime(MethodBase method) - { - foreach (ParameterInfo parameter in method.GetParameters()) - { - object[] attributes = parameter.GetCustomAttributes(typeof(ParameterAttribute), false); - if (attributes.Length > 1) - { - throw new NConsolerException("More than one attribute is applied to the parameter \"{0}\" in the method \"{1}\"", parameter.Name, method.Name); - } - } - } - - private static bool CanBeNull(Type type) - { - return type == typeof(string) - || type == typeof(string[]) - || type == typeof(int[]); - } - - private static void CheckOptionalParametersDefaultValuesAreAssignableToRealParameterTypes(MethodBase method) - { - foreach (ParameterInfo parameter in method.GetParameters()) - { - if (IsRequired(parameter)) - { - continue; - } - OptionalAttribute optional = GetOptional(parameter); -// this causes a lot of unnecessary exceptions which is annoying during debug -// if (optional.Default != null && optional.Default.GetType() == typeof(string) && CanBeConvertedToDate(optional.Default.ToString())) -// { -// return; -// } - if ((optional.Default == null && !CanBeNull(parameter.ParameterType)) - || (optional.Default != null && !optional.Default.GetType().IsAssignableFrom(parameter.ParameterType))) - { - throw new NConsolerException("Default value for an optional parameter \"{0}\" in method \"{1}\" can not be assigned to the parameter", parameter.Name, method.Name); - } - } - } - - private static void CheckOptionalParametersAreAfterRequiredOnes(MethodBase method) - { - bool optionalFound = false; - foreach (ParameterInfo parameter in method.GetParameters()) - { - if (IsOptional(parameter)) - { - optionalFound = true; - } - else if (optionalFound) - { - throw new NConsolerException("It is not allowed to write a parameter with a Required attribute after a parameter with an Optional one. See method \"{0}\" parameter \"{1}\"", method.Name, parameter.Name); - } - } - } - - private static void CheckOptionalParametersAltNamesAreNotDuplicated(MethodBase method) - { - List parameterNames = new List(); - foreach (ParameterInfo parameter in method.GetParameters()) - { - if (IsRequired(parameter)) - { - parameterNames.Add(parameter.Name.ToLower()); - } - else - { - if (parameterNames.Contains(parameter.Name.ToLower())) - { - throw new NConsolerException("Found duplicated parameter name \"{0}\" in method \"{1}\". Please check alt names for optional parameters", parameter.Name, method.Name); - } - parameterNames.Add(parameter.Name.ToLower()); - OptionalAttribute optional = GetOptional(parameter); - foreach (string altName in optional.AltNames) - { - if (parameterNames.Contains(altName.ToLower())) - { - throw new NConsolerException("Found duplicated parameter name \"{0}\" in method \"{1}\". Please check alt names for optional parameters", altName, method.Name); - } - parameterNames.Add(altName.ToLower()); - } - } - } - } - - #endregion - } - - /// - /// Used for getting messages from NConsoler - /// - public interface IMessenger - { - void Write(string message); - } - - /// - /// Uses Console class for message output - /// - public class ConsoleMessenger : IMessenger - { - public void Write(string message) - { - Console.WriteLine(message); - } - } - - /// - /// Every action method should be marked with this attribute - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public sealed class ActionAttribute : Attribute - { - public ActionAttribute() - { - } - - public ActionAttribute(string description) - { - _description = description; - } - - private string _description = String.Empty; - - /// - /// Description is used for help messages - /// - public string Description - { - get - { - return _description; - } - - set - { - _description = value; - } - } - } - - /// - /// Should not be used directly - /// - [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] - public class ParameterAttribute : Attribute - { - private string _description = String.Empty; - - /// - /// Description is used in help message - /// - public string Description - { - get - { - return _description; - } - - set - { - _description = value; - } - } - - protected ParameterAttribute() - { - } - } - - /// - /// Marks an Action method parameter as optional - /// - public sealed class OptionalAttribute : ParameterAttribute - { - private string[] _altNames; - - public string[] AltNames - { - get - { - return _altNames; - } - - set - { - _altNames = value; - } - } - - private readonly object _defaultValue; - - public object Default - { - get - { - return _defaultValue; - } - } - - /// Default value if client doesn't pass this value - /// Aliases for parameter - public OptionalAttribute(object defaultValue, params string[] altNames) - { - _defaultValue = defaultValue; - _altNames = altNames; - } - } - - /// - /// Marks an Action method parameter as required - /// - public sealed class RequiredAttribute : ParameterAttribute - { - - } - - /// - /// Can be used for safe exception throwing - NConsoler will catch the exception - /// - public sealed class NConsolerException : Exception - { - public NConsolerException() - { - } - - public NConsolerException(string message, Exception innerException) - : base(message, innerException) - { - } - - public NConsolerException(string message, params string[] arguments) - : base(String.Format(message, arguments)) - { - } - } -} diff --git a/GeneratorTools/Program.cs b/GeneratorTools/Program.cs deleted file mode 100644 index 1cd34b66a..000000000 --- a/GeneratorTools/Program.cs +++ /dev/null @@ -1,164 +0,0 @@ -using System; -using System.CodeDom; -using System.CodeDom.Compiler; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using Microsoft.CSharp; -using NConsoler; -using TechTalk.SpecFlow.Parser; -using TechTalk.SpecFlow.Parser.Configuration; -using TechTalk.SpecFlow.Parser.SyntaxElements; -using TechTalk.SpecFlow.Reporting; - -namespace TechTalk.SpecFlow.Runner -{ - class Program - { - static void Main(string[] args) - { - Consolery.Run(typeof(Program), args); - return; - } - - [Action("Process all feature files")] - public static void All( - [Required] string projectFile - ) - { - SpecFlowProject specFlowProject = MsBuildProjectReader.LoadSpecFlowProjectFromMsBuild(projectFile); - - ProcessProject(specFlowProject); - -// Console.ReadLine(); - } - - [Action("Run all feature files")] - public static void Run( - [Required] string projectFile - ) - { - SpecFlowProject specFlowProject = MsBuildProjectReader.LoadSpecFlowProjectFromMsBuild(projectFile); - - RunProject(specFlowProject); - - Console.ReadLine(); - } - - private static void RunProject(SpecFlowProject specFlowProject) - { - Console.WriteLine("Processing project: " + specFlowProject); -// -// AssemblyName name = AssemblyName.GetAssemblyName( -// @"C:\Users\jba\Dev\Projects\SpecFlow\Samples\BowlingKata\Bowling.Specflow\bin\Debug\Bowling.dll"); -// AppDomain.CurrentDomain.Load(name); - - ITestRunner testRunner = new TestRunner(); - - List bindingAssemblies = new List(); - - string projectAssemblyPath = specFlowProject.ProjectFolder + @"\bin\Debug\" + specFlowProject.AssemblyName + ".dll"; - Assembly projectAssembly = Assembly.LoadFrom(projectAssemblyPath); - bindingAssemblies.Add(projectAssembly); -// string projectAssemblyDir = specFlowProject.ProjectFolder + @"\bin\Debug\"; -// string[] assemblies = Directory.GetFiles(projectAssemblyDir, "*.dll"); -// foreach (string assembly in assemblies) -// { -// Assembly projectAssembly2 = Assembly.LoadFrom(assembly); -// AssemblyName name2 = AssemblyName.GetAssemblyName(assembly); -// AppDomain.CurrentDomain.Load(name2); -//// bindingAssemblies.Add(projectAssembly2); -// } - - testRunner.InitializeTestRunner(bindingAssemblies.ToArray()); - - - foreach (var featureFile in specFlowProject.FeatureFiles) - { - SpecFlowLangParser parser = new SpecFlowLangParser(specFlowProject.GeneratorConfiguration.FeatureLanguage); - Feature feature = parser.Parse(new StringReader(File.ReadAllText(featureFile.GetFullPath(specFlowProject))), featureFile.GetFullPath(specFlowProject)); - - TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en"), "Score Calculation", "In order to know my performance\r\nAs a player\r\nI want the system to calculate my t" + -"otal score", ((string[])(null))); - testRunner.OnFeatureStart(featureInfo); - - foreach (Scenario scenario in feature.Scenarios) - { - TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Gutter game", ((string[])(null))); - testRunner.OnScenarioStart(scenarioInfo); - - foreach (ScenarioStep scenarioStep in scenario.Steps) - { - TechTalk.SpecFlow.Table table1 = null; - if (scenarioStep.TableArg != null) - { - Console.WriteLine("Table: " + scenarioStep.TableArg); - - table1 = new TechTalk.SpecFlow.Table(new string[] { "Pins" }); - foreach (Row row in scenarioStep.TableArg.Body) - { - string[] cells = new string[row.Cells.Count()]; - for (int i = 0; i < row.Cells.Count(); i++ ) - { - cells[i] = row.Cells[i].Value; - } - table1.AddRow(cells); - } - - } - - if (scenarioStep is Given) - { - Console.WriteLine("Given: " + scenarioStep.Text); - testRunner.Given(scenarioStep.Text); - } - else if (scenarioStep is When) - { - Console.WriteLine("When: " + scenarioStep.Text); - testRunner.When(scenarioStep.Text, null, table1); - } - else if (scenarioStep is Then) - { - Console.WriteLine("Then: " + scenarioStep.Text); - testRunner.Then(scenarioStep.Text); - } - else if (scenarioStep is And) - { - Console.WriteLine("And: " + scenarioStep.Text); - testRunner.And(scenarioStep.Text); - } - } - testRunner.OnScenarioEnd(); - } - testRunner.OnFeatureEnd(); - } - } - - private static void ProcessProject(SpecFlowProject specFlowProject) - { - Console.WriteLine("Processing project: " + specFlowProject); - - foreach (var featureFile in specFlowProject.FeatureFiles) - { - string featureFileFullPath = featureFile.GetFullPath(specFlowProject); - - Console.WriteLine("Processing file: " + featureFileFullPath); - - SpecFlowGenerator generator = new SpecFlowGenerator(specFlowProject); - CodeCompileUnit compileUnit = generator.GenerateTestFileCode(featureFile); - - string codeFileFullPath = featureFileFullPath + ".cs"; - using (var writer = new StreamWriter(codeFileFullPath, false, Encoding.UTF8)) - { - CSharpCodeProvider codeProvider = new CSharpCodeProvider(); - CodeGeneratorOptions options = new CodeGeneratorOptions(); - options.BracingStyle = "C"; - codeProvider.GenerateCodeFromCompileUnit(compileUnit, writer, options); - } - } - } - } -} diff --git a/Installer/SpecFlowSetup/SpecFlowSetup.vdproj b/Installer/SpecFlowSetup/SpecFlowSetup.vdproj index f5f868f97..4c32ab2c6 100644 --- a/Installer/SpecFlowSetup/SpecFlowSetup.vdproj +++ b/Installer/SpecFlowSetup/SpecFlowSetup.vdproj @@ -45,8 +45,32 @@ } "Entry" { + "MsmKey" = "8:_109CA2795B9A7ADE61C883D41F3F86F5" + "OwnerKey" = "8:_75A70548B8444C668B2D0462EC9CC366" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { "MsmKey" = "8:_12B423DBA56C8DC0E3B05977AFDCC8BB" - "OwnerKey" = "8:_F568BBC0422746FA9ADF497EFE4B6809" + "OwnerKey" = "8:_8B2D5366F6C941138FF83318D8C65C46" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_12B423DBA56C8DC0E3B05977AFDCC8BB" + "OwnerKey" = "8:_6E1CCF469FF44C51440438B84FAA9363" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_12B423DBA56C8DC0E3B05977AFDCC8BB" + "OwnerKey" = "8:_75A70548B8444C668B2D0462EC9CC366" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_12B423DBA56C8DC0E3B05977AFDCC8BB" + "OwnerKey" = "8:_8B7827FFE66F143641790B9E6A834514" "MsmSig" = "8:_UNDEFINED" } "Entry" @@ -94,13 +118,25 @@ "Entry" { "MsmKey" = "8:_277A812A65804F067023A54D435F95E9" - "OwnerKey" = "8:_8B2D5366F6C941138FF83318D8C65C46" + "OwnerKey" = "8:_8B7827FFE66F143641790B9E6A834514" "MsmSig" = "8:_UNDEFINED" } "Entry" { "MsmKey" = "8:_277A812A65804F067023A54D435F95E9" - "OwnerKey" = "8:_F568BBC0422746FA9ADF497EFE4B6809" + "OwnerKey" = "8:_6E1CCF469FF44C51440438B84FAA9363" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_277A812A65804F067023A54D435F95E9" + "OwnerKey" = "8:_75A70548B8444C668B2D0462EC9CC366" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_277A812A65804F067023A54D435F95E9" + "OwnerKey" = "8:_8B2D5366F6C941138FF83318D8C65C46" "MsmSig" = "8:_UNDEFINED" } "Entry" @@ -196,19 +232,19 @@ "Entry" { "MsmKey" = "8:_64C0AC2C36F206D7B415B1588A67AB7D" - "OwnerKey" = "8:_8B2D5366F6C941138FF83318D8C65C46" + "OwnerKey" = "8:_277A812A65804F067023A54D435F95E9" "MsmSig" = "8:_UNDEFINED" } "Entry" { "MsmKey" = "8:_64C0AC2C36F206D7B415B1588A67AB7D" - "OwnerKey" = "8:_F568BBC0422746FA9ADF497EFE4B6809" + "OwnerKey" = "8:_75A70548B8444C668B2D0462EC9CC366" "MsmSig" = "8:_UNDEFINED" } "Entry" { "MsmKey" = "8:_64C0AC2C36F206D7B415B1588A67AB7D" - "OwnerKey" = "8:_277A812A65804F067023A54D435F95E9" + "OwnerKey" = "8:_8B2D5366F6C941138FF83318D8C65C46" "MsmSig" = "8:_UNDEFINED" } "Entry" @@ -249,12 +285,36 @@ } "Entry" { + "MsmKey" = "8:_6E1CCF469FF44C51440438B84FAA9363" + "OwnerKey" = "8:_75A70548B8444C668B2D0462EC9CC366" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { "MsmKey" = "8:_72BA062375FF32683B46885AB8FB9F3C" "OwnerKey" = "8:_12B423DBA56C8DC0E3B05977AFDCC8BB" "MsmSig" = "8:_UNDEFINED" } "Entry" { + "MsmKey" = "8:_72BA062375FF32683B46885AB8FB9F3C" + "OwnerKey" = "8:_109CA2795B9A7ADE61C883D41F3F86F5" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_72BA062375FF32683B46885AB8FB9F3C" + "OwnerKey" = "8:_75A70548B8444C668B2D0462EC9CC366" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_75A70548B8444C668B2D0462EC9CC366" + "OwnerKey" = "8:_UNDEFINED" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { "MsmKey" = "8:_76A50A8C2366870C6FF9029C08057BC1" "OwnerKey" = "8:_CDB5EF528F7640FEF9EE58B3A2D014B2" "MsmSig" = "8:_UNDEFINED" @@ -328,7 +388,13 @@ "Entry" { "MsmKey" = "8:_887B7AF445E3CB62E718707DB1A2DB74" - "OwnerKey" = "8:_F568BBC0422746FA9ADF497EFE4B6809" + "OwnerKey" = "8:_6E1CCF469FF44C51440438B84FAA9363" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_887B7AF445E3CB62E718707DB1A2DB74" + "OwnerKey" = "8:_75A70548B8444C668B2D0462EC9CC366" "MsmSig" = "8:_UNDEFINED" } "Entry" @@ -346,6 +412,12 @@ "Entry" { "MsmKey" = "8:_887B7AF445E3CB62E718707DB1A2DB74" + "OwnerKey" = "8:_8B7827FFE66F143641790B9E6A834514" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_887B7AF445E3CB62E718707DB1A2DB74" "OwnerKey" = "8:_8B2D5366F6C941138FF83318D8C65C46" "MsmSig" = "8:_UNDEFINED" } @@ -369,6 +441,24 @@ } "Entry" { + "MsmKey" = "8:_8B7827FFE66F143641790B9E6A834514" + "OwnerKey" = "8:_8B2D5366F6C941138FF83318D8C65C46" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_8B7827FFE66F143641790B9E6A834514" + "OwnerKey" = "8:_6E1CCF469FF44C51440438B84FAA9363" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_8B7827FFE66F143641790B9E6A834514" + "OwnerKey" = "8:_75A70548B8444C668B2D0462EC9CC366" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { "MsmKey" = "8:_8C9C7FF1DB4641FF874CFFD3CFF10E62" "OwnerKey" = "8:_UNDEFINED" "MsmSig" = "8:_UNDEFINED" @@ -382,7 +472,7 @@ "Entry" { "MsmKey" = "8:_ABD5677C265A4D7BC34829F543181C87" - "OwnerKey" = "8:_F568BBC0422746FA9ADF497EFE4B6809" + "OwnerKey" = "8:_75A70548B8444C668B2D0462EC9CC366" "MsmSig" = "8:_UNDEFINED" } "Entry" @@ -508,7 +598,7 @@ "Entry" { "MsmKey" = "8:_ED3D10E989AADCAA0A7443322308EE1F" - "OwnerKey" = "8:_F568BBC0422746FA9ADF497EFE4B6809" + "OwnerKey" = "8:_75A70548B8444C668B2D0462EC9CC366" "MsmSig" = "8:_UNDEFINED" } "Entry" @@ -531,12 +621,6 @@ } "Entry" { - "MsmKey" = "8:_F568BBC0422746FA9ADF497EFE4B6809" - "OwnerKey" = "8:_UNDEFINED" - "MsmSig" = "8:_UNDEFINED" - } - "Entry" - { "MsmKey" = "8:_FCA7A8942BCF47809C58574D51DA4A7B" "OwnerKey" = "8:_UNDEFINED" "MsmSig" = "8:_UNDEFINED" @@ -550,19 +634,19 @@ "Entry" { "MsmKey" = "8:_UNDEFINED" - "OwnerKey" = "8:_F568BBC0422746FA9ADF497EFE4B6809" + "OwnerKey" = "8:_75A70548B8444C668B2D0462EC9CC366" "MsmSig" = "8:_UNDEFINED" } "Entry" { "MsmKey" = "8:_UNDEFINED" - "OwnerKey" = "8:_12B423DBA56C8DC0E3B05977AFDCC8BB" + "OwnerKey" = "8:_6E1CCF469FF44C51440438B84FAA9363" "MsmSig" = "8:_UNDEFINED" } "Entry" { "MsmKey" = "8:_UNDEFINED" - "OwnerKey" = "8:_72BA062375FF32683B46885AB8FB9F3C" + "OwnerKey" = "8:_109CA2795B9A7ADE61C883D41F3F86F5" "MsmSig" = "8:_UNDEFINED" } "Entry" @@ -580,6 +664,12 @@ "Entry" { "MsmKey" = "8:_UNDEFINED" + "OwnerKey" = "8:_8B7827FFE66F143641790B9E6A834514" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_UNDEFINED" "OwnerKey" = "8:_277A812A65804F067023A54D435F95E9" "MsmSig" = "8:_UNDEFINED" } @@ -592,6 +682,18 @@ "Entry" { "MsmKey" = "8:_UNDEFINED" + "OwnerKey" = "8:_12B423DBA56C8DC0E3B05977AFDCC8BB" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_UNDEFINED" + "OwnerKey" = "8:_72BA062375FF32683B46885AB8FB9F3C" + "MsmSig" = "8:_UNDEFINED" + } + "Entry" + { + "MsmKey" = "8:_UNDEFINED" "OwnerKey" = "8:_3062EBEC857307CE646864E951AD7CF4" "MsmSig" = "8:_UNDEFINED" } @@ -922,6 +1024,37 @@ "IsDependency" = "11:TRUE" "IsolateTo" = "8:" } + "{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_109CA2795B9A7ADE61C883D41F3F86F5" + { + "AssemblyRegister" = "3:1" + "AssemblyIsInGAC" = "11:FALSE" + "AssemblyAsmDisplayName" = "8:Microsoft.Build.Utilities.v3.5, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" + "ScatterAssemblies" + { + "_109CA2795B9A7ADE61C883D41F3F86F5" + { + "Name" = "8:Microsoft.Build.Utilities.v3.5.dll" + "Attributes" = "3:512" + } + } + "SourcePath" = "8:Microsoft.Build.Utilities.v3.5.dll" + "TargetName" = "8:" + "Tag" = "8:" + "Folder" = "8:_2D85F2CCE0F64901A8231E38E3C0F2A8" + "Condition" = "8:" + "Transitive" = "11:FALSE" + "Vital" = "11:TRUE" + "ReadOnly" = "11:FALSE" + "Hidden" = "11:FALSE" + "System" = "11:FALSE" + "Permanent" = "11:FALSE" + "SharedLegacy" = "11:FALSE" + "PackageAs" = "3:1" + "Register" = "3:1" + "Exclude" = "11:TRUE" + "IsDependency" = "11:TRUE" + "IsolateTo" = "8:" + } "{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_12B423DBA56C8DC0E3B05977AFDCC8BB" { "AssemblyRegister" = "3:1" @@ -1241,6 +1374,37 @@ "IsDependency" = "11:TRUE" "IsolateTo" = "8:" } + "{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_6E1CCF469FF44C51440438B84FAA9363" + { + "AssemblyRegister" = "3:1" + "AssemblyIsInGAC" = "11:FALSE" + "AssemblyAsmDisplayName" = "8:TechTalk.SpecFlow.Reporting, Version=1.1.0.0, Culture=neutral, PublicKeyToken=0778194805d6db41, processorArchitecture=MSIL" + "ScatterAssemblies" + { + "_6E1CCF469FF44C51440438B84FAA9363" + { + "Name" = "8:TechTalk.SpecFlow.Reporting.dll" + "Attributes" = "3:512" + } + } + "SourcePath" = "8:TechTalk.SpecFlow.Reporting.dll" + "TargetName" = "8:" + "Tag" = "8:" + "Folder" = "8:_2D85F2CCE0F64901A8231E38E3C0F2A8" + "Condition" = "8:" + "Transitive" = "11:FALSE" + "Vital" = "11:TRUE" + "ReadOnly" = "11:FALSE" + "Hidden" = "11:FALSE" + "System" = "11:FALSE" + "Permanent" = "11:FALSE" + "SharedLegacy" = "11:FALSE" + "PackageAs" = "3:1" + "Register" = "3:1" + "Exclude" = "11:FALSE" + "IsDependency" = "11:TRUE" + "IsolateTo" = "8:" + } "{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_72BA062375FF32683B46885AB8FB9F3C" { "AssemblyRegister" = "3:1" @@ -1427,6 +1591,37 @@ "IsDependency" = "11:TRUE" "IsolateTo" = "8:" } + "{9F6F8455-1EF1-4B85-886A-4223BCC8E7F7}:_8B7827FFE66F143641790B9E6A834514" + { + "AssemblyRegister" = "3:1" + "AssemblyIsInGAC" = "11:FALSE" + "AssemblyAsmDisplayName" = "8:TechTalk.SpecFlow.Generator, Version=1.1.0.0, Culture=neutral, PublicKeyToken=0778194805d6db41, processorArchitecture=MSIL" + "ScatterAssemblies" + { + "_8B7827FFE66F143641790B9E6A834514" + { + "Name" = "8:TechTalk.SpecFlow.Generator.dll" + "Attributes" = "3:512" + } + } + "SourcePath" = "8:TechTalk.SpecFlow.Generator.dll" + "TargetName" = "8:" + "Tag" = "8:" + "Folder" = "8:_2D85F2CCE0F64901A8231E38E3C0F2A8" + "Condition" = "8:" + "Transitive" = "11:FALSE" + "Vital" = "11:TRUE" + "ReadOnly" = "11:FALSE" + "Hidden" = "11:FALSE" + "System" = "11:FALSE" + "Permanent" = "11:FALSE" + "SharedLegacy" = "11:FALSE" + "PackageAs" = "3:1" + "Register" = "3:1" + "Exclude" = "11:FALSE" + "IsDependency" = "11:TRUE" + "IsolateTo" = "8:" + } "{1FB2D0AE-D3B9-43D4-B9DD-F88EC61E35DE}:_8C9C7FF1DB4641FF874CFFD3CFF10E62" { "SourcePath" = "8:..\\..\\VsIntegration\\obj\\Debug\\SpecFlowEventDefinition.zip" @@ -2684,9 +2879,9 @@ { } } - "{5259A561-127C-4D43-A0A1-72F10C7B3BF8}:_8B2D5366F6C941138FF83318D8C65C46" + "{5259A561-127C-4D43-A0A1-72F10C7B3BF8}:_75A70548B8444C668B2D0462EC9CC366" { - "SourcePath" = "8:..\\..\\VsIntegration\\obj\\Debug\\TechTalk.SpecFlow.VsIntegration.dll" + "SourcePath" = "8:..\\..\\Tools\\obj\\Debug\\specflow.exe" "TargetName" = "8:" "Tag" = "8:" "Folder" = "8:_2D85F2CCE0F64901A8231E38E3C0F2A8" @@ -2706,18 +2901,18 @@ "ProjectOutputGroupRegister" = "3:1" "OutputConfiguration" = "8:" "OutputGroupCanonicalName" = "8:Built" - "OutputProjectGuid" = "8:{5703CA95-A08A-46AE-AE24-DB6B21FD6F7E}" + "OutputProjectGuid" = "8:{87BE7FE6-C3DE-4409-ABF6-FA5B60AF3DE1}" "ShowKeyOutput" = "11:TRUE" "ExcludeFilters" { } } - "{5259A561-127C-4D43-A0A1-72F10C7B3BF8}:_F40F425B3B5D4365A81330EA775FCC1B" + "{5259A561-127C-4D43-A0A1-72F10C7B3BF8}:_8B2D5366F6C941138FF83318D8C65C46" { - "SourcePath" = "8:..\\DevenvSetupCustomAction\\obj\\Debug\\DevenvSetupCustomAction.dll" + "SourcePath" = "8:..\\..\\VsIntegration\\obj\\Debug\\TechTalk.SpecFlow.VsIntegration.dll" "TargetName" = "8:" "Tag" = "8:" - "Folder" = "8:_256850BB5BF44587B0F43F11538A0DA3" + "Folder" = "8:_2D85F2CCE0F64901A8231E38E3C0F2A8" "Condition" = "8:" "Transitive" = "11:FALSE" "Vital" = "11:TRUE" @@ -2734,18 +2929,18 @@ "ProjectOutputGroupRegister" = "3:1" "OutputConfiguration" = "8:" "OutputGroupCanonicalName" = "8:Built" - "OutputProjectGuid" = "8:{02F3B86D-0421-4D45-A701-44F3C94CF07D}" + "OutputProjectGuid" = "8:{5703CA95-A08A-46AE-AE24-DB6B21FD6F7E}" "ShowKeyOutput" = "11:TRUE" "ExcludeFilters" { } } - "{5259A561-127C-4D43-A0A1-72F10C7B3BF8}:_F568BBC0422746FA9ADF497EFE4B6809" + "{5259A561-127C-4D43-A0A1-72F10C7B3BF8}:_F40F425B3B5D4365A81330EA775FCC1B" { - "SourcePath" = "8:..\\..\\Reporting\\obj\\Debug\\TechTalk.SpecFlow.Reporting.exe" + "SourcePath" = "8:..\\DevenvSetupCustomAction\\obj\\Debug\\DevenvSetupCustomAction.dll" "TargetName" = "8:" "Tag" = "8:" - "Folder" = "8:_2D85F2CCE0F64901A8231E38E3C0F2A8" + "Folder" = "8:_256850BB5BF44587B0F43F11538A0DA3" "Condition" = "8:" "Transitive" = "11:FALSE" "Vital" = "11:TRUE" @@ -2762,7 +2957,7 @@ "ProjectOutputGroupRegister" = "3:1" "OutputConfiguration" = "8:" "OutputGroupCanonicalName" = "8:Built" - "OutputProjectGuid" = "8:{FC43509F-E7D3-40C4-B4C3-1E6C9D5530A4}" + "OutputProjectGuid" = "8:{02F3B86D-0421-4D45-A701-44F3C94CF07D}" "ShowKeyOutput" = "11:TRUE" "ExcludeFilters" { diff --git a/Parser/TechTalk.SpecFlow.Parser.csproj b/Parser/TechTalk.SpecFlow.Parser.csproj index 868e164b9..12a152097 100644 --- a/Parser/TechTalk.SpecFlow.Parser.csproj +++ b/Parser/TechTalk.SpecFlow.Parser.csproj @@ -45,9 +45,7 @@ False ..\lib\antlr\Antlr3.Runtime.dll - - 3.5 @@ -61,18 +59,9 @@ - - Configuration\ConfigurationSectionHandler.cs - - - StringExtensions.cs - VersionInfo.cs - - - @@ -92,8 +81,6 @@ - - @@ -111,7 +98,6 @@ - @@ -129,4 +115,4 @@ --> - \ No newline at end of file + diff --git a/Reporting/BindingCollector.cs b/Reporting/BindingCollector.cs index 7fde82ae0..840153c42 100644 --- a/Reporting/BindingCollector.cs +++ b/Reporting/BindingCollector.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Reflection; using System.Text.RegularExpressions; -using TechTalk.SpecFlow.Parser.Configuration; +using TechTalk.SpecFlow.Generator.Configuration; namespace TechTalk.SpecFlow.Reporting { diff --git a/Reporting/MsBuildProjectReader.cs b/Reporting/MsBuildProjectReader.cs index 807d80058..61474d4b1 100644 --- a/Reporting/MsBuildProjectReader.cs +++ b/Reporting/MsBuildProjectReader.cs @@ -2,7 +2,7 @@ using System.IO; using System.Linq; using Microsoft.Build.BuildEngine; -using TechTalk.SpecFlow.Parser.Configuration; +using TechTalk.SpecFlow.Generator.Configuration; namespace TechTalk.SpecFlow.Reporting { diff --git a/Reporting/NUnitExecutionReport/NUnitExecutionReportGenerator.cs b/Reporting/NUnitExecutionReport/NUnitExecutionReportGenerator.cs index 12e457247..79038052e 100644 --- a/Reporting/NUnitExecutionReport/NUnitExecutionReportGenerator.cs +++ b/Reporting/NUnitExecutionReport/NUnitExecutionReportGenerator.cs @@ -6,18 +6,25 @@ using System.Text; using System.Xml; using System.Xml.Serialization; -using TechTalk.SpecFlow.Parser.Configuration; +using TechTalk.SpecFlow.Generator.Configuration; using TechTalk.SpecFlow.Reporting.NUnitExecutionReport.ReportElements; namespace TechTalk.SpecFlow.Reporting.NUnitExecutionReport { - internal class NUnitExecutionReportGenerator + public class NUnitExecutionReportGenerator { private ReportElements.NUnitExecutionReport report; - private SpecFlowProject specFlowProject; + private readonly SpecFlowProject specFlowProject; private readonly string xmlTestResultPath; private readonly string labeledTestOutputPath; + public NUnitExecutionReportGenerator(string projectFile, string xmlTestResultPath, string labeledTestOutputPath) + { + this.xmlTestResultPath = xmlTestResultPath; + this.specFlowProject = MsBuildProjectReader.LoadSpecFlowProjectFromMsBuild(projectFile); + this.labeledTestOutputPath = labeledTestOutputPath; + } + public NUnitExecutionReportGenerator(SpecFlowProject specFlowProject, string xmlTestResultPath, string labeledTestOutputPath) { this.xmlTestResultPath = xmlTestResultPath; diff --git a/Reporting/ParserHelper.cs b/Reporting/ParserHelper.cs index 43b3d6cad..8c5578053 100644 --- a/Reporting/ParserHelper.cs +++ b/Reporting/ParserHelper.cs @@ -3,8 +3,8 @@ using System.Globalization; using System.IO; using System.Linq; +using TechTalk.SpecFlow.Generator.Configuration; using TechTalk.SpecFlow.Parser; -using TechTalk.SpecFlow.Parser.Configuration; using TechTalk.SpecFlow.Parser.SyntaxElements; namespace TechTalk.SpecFlow.Reporting diff --git a/Reporting/Program.cs b/Reporting/Program.cs index 901572807..bdb466df6 100644 --- a/Reporting/Program.cs +++ b/Reporting/Program.cs @@ -1,8 +1,8 @@ -using System; +/*using System; using System.Collections.Generic; using System.IO; using NConsoler; -using TechTalk.SpecFlow.Parser.Configuration; +using TechTalk.SpecFlow.Generator.Configuration; using TechTalk.SpecFlow.Parser.SyntaxElements; using TechTalk.SpecFlow.Reporting.NUnitExecutionReport; using TechTalk.SpecFlow.Reporting.StepDefinitionReport; @@ -24,19 +24,9 @@ public static void StepDefinitionReport( [Optional("StepDefinitionReport.html", "out")] string outputFile ) { - SpecFlowProject specFlowProject = MsBuildProjectReader.LoadSpecFlowProjectFromMsBuild(projectFile); - - List parsedFeatures = ParserHelper.GetParsedFeatures(specFlowProject); - - var basePath = Path.Combine(specFlowProject.ProjectFolder, binFolder); - List bindings = BindingCollector.CollectBindings(specFlowProject, basePath); - - StepDefinitionReportGenerator generator = new StepDefinitionReportGenerator(specFlowProject, bindings, parsedFeatures, - true); + StepDefinitionReportGenerator generator = new StepDefinitionReportGenerator(projectFile, binFolder, true); generator.GenerateReport(); - - string outputFilePath = Path.GetFullPath(outputFile); - generator.TransformReport(outputFilePath); + generator.TransformReport(Path.GetFullPath(outputFile)); } [Action] @@ -47,16 +37,12 @@ public static void NUnitExecutionReport( [Optional("TestResult.html", "out")] string outputFile ) { - SpecFlowProject specFlowProject = MsBuildProjectReader.LoadSpecFlowProjectFromMsBuild(projectFile); - NUnitExecutionReportGenerator generator = new NUnitExecutionReportGenerator( - specFlowProject, + projectFile, Path.GetFullPath(xmlTestResult), Path.GetFullPath(labeledTestOutput)); generator.GenerateReport(); - - string outputFilePath = Path.GetFullPath(outputFile); - generator.TransformReport(outputFilePath); + generator.TransformReport(Path.GetFullPath(outputFile)); } } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/Reporting/StepDefinitionReport/StepDefinitionReportGenerator.cs b/Reporting/StepDefinitionReport/StepDefinitionReportGenerator.cs index 2cae1da07..ff8605cdd 100644 --- a/Reporting/StepDefinitionReport/StepDefinitionReportGenerator.cs +++ b/Reporting/StepDefinitionReport/StepDefinitionReportGenerator.cs @@ -9,13 +9,13 @@ using System.Xml; using System.Xml.Serialization; using System.Xml.Xsl; -using TechTalk.SpecFlow.Parser.Configuration; +using TechTalk.SpecFlow.Generator.Configuration; using TechTalk.SpecFlow.Parser.SyntaxElements; using TechTalk.SpecFlow.Reporting.StepDefinitionReport.ReportElements; namespace TechTalk.SpecFlow.Reporting.StepDefinitionReport { - internal class StepDefinitionReportGenerator + public class StepDefinitionReportGenerator { private readonly SpecFlowProject specFlowProject; private readonly List bindings; @@ -27,6 +27,18 @@ internal class StepDefinitionReportGenerator private Dictionary bindingByStepDef; private readonly List stepDefsWithNoBinding = new List(); + public StepDefinitionReportGenerator(string projectFile, string binFolder, bool showBindingsWithoutInsance) + { + specFlowProject = MsBuildProjectReader.LoadSpecFlowProjectFromMsBuild(projectFile); + + parsedFeatures = ParserHelper.GetParsedFeatures(specFlowProject); + + var basePath = Path.Combine(specFlowProject.ProjectFolder, binFolder); + bindings = BindingCollector.CollectBindings(specFlowProject, basePath); + + this.showBindingsWithoutInsance = showBindingsWithoutInsance; + } + public StepDefinitionReportGenerator(SpecFlowProject specFlowProject, List bindings, List parsedFeatures, bool showBindingsWithoutInsance) { this.specFlowProject = specFlowProject; diff --git a/Reporting/TechTalk.SpecFlow.Reporting.csproj b/Reporting/TechTalk.SpecFlow.Reporting.csproj index 05414d031..2ece5eeaa 100644 --- a/Reporting/TechTalk.SpecFlow.Reporting.csproj +++ b/Reporting/TechTalk.SpecFlow.Reporting.csproj @@ -6,7 +6,7 @@ 9.0.30729 2.0 {FC43509F-E7D3-40C4-B4C3-1E6C9D5530A4} - Exe + Library Properties TechTalk.SpecFlow.Reporting TechTalk.SpecFlow.Reporting @@ -22,6 +22,8 @@ true ..\specflow.snk + + true @@ -62,7 +64,6 @@ - @@ -89,6 +90,10 @@ + + {453D8014-B6CD-4E86-80A8-D59F59092334} + TechTalk.SpecFlow.Generator + {7CCEF6D6-FC17-422E-9BED-EDD752B6496F} TechTalk.SpecFlow.Parser diff --git a/Reporting/XsltHelper.cs b/Reporting/XsltHelper.cs index f3d45536a..b4ce57683 100644 --- a/Reporting/XsltHelper.cs +++ b/Reporting/XsltHelper.cs @@ -6,7 +6,7 @@ using System.Xml; using System.Xml.Serialization; using System.Xml.Xsl; -using TechTalk.SpecFlow.Parser.Configuration; +using TechTalk.SpecFlow.Generator.Configuration; namespace TechTalk.SpecFlow.Reporting { diff --git a/Runtime/Configuration/ConfigurationSectionHandler.cs b/Runtime/Configuration/ConfigurationSectionHandler.cs index 4b28f3651..dbd3b3e00 100644 --- a/Runtime/Configuration/ConfigurationSectionHandler.cs +++ b/Runtime/Configuration/ConfigurationSectionHandler.cs @@ -32,6 +32,31 @@ public static class ConfigDefaults internal const bool AllowDebugGeneratedFiles = false; } + public static class ConfigurationServices + { + public static TInterface CreateInstance(Type type) + { + // do not use ErrorProvider for thowing exceptions here, because of the potential + // infinite loop + try + { + return (TInterface)Activator.CreateInstance(type); + } + catch(InvalidCastException) + { + throw new ConfigurationErrorsException( + String.Format("The specified type '{0}' does not implement interface '{1}'", + type.FullName, typeof(TInterface).FullName)); + } + catch(Exception ex) + { + throw new ConfigurationErrorsException( + String.Format("Unable to create instance of type '{0}': {1}", + type.FullName, ex.Message), ex); + } + } + } + partial class ConfigurationSectionHandler : ConfigurationSection { [ConfigurationProperty("language", IsRequired = false)] diff --git a/Runtime/ObjectContainer.cs b/Runtime/ObjectContainer.cs index a9225d6ba..1d153bda7 100644 --- a/Runtime/ObjectContainer.cs +++ b/Runtime/ObjectContainer.cs @@ -203,7 +203,7 @@ public static IUnitTestRuntimeProvider UnitTestRuntimeProvider #region factory helper methods private static TInterface GetOrCreate(ref TInterface storage, Type implementationType) where TInterface : class { - return GetOrCreate(ref storage, () => CreateInstance(implementationType)); + return GetOrCreate(ref storage, () => ConfigurationServices.CreateInstance(implementationType)); } private static TClass GetOrCreate(ref TClass storage) where TClass : class, new() @@ -220,27 +220,6 @@ private static TInterface GetOrCreate(ref TInterface storage, Func(Type type) - { - // do not use ErrorProvider for thowing exceptions here, because of the potential - // infinite loop - try - { - return (TInterface)Activator.CreateInstance(type); - } - catch(InvalidCastException) - { - throw new ConfigurationErrorsException( - string.Format("The specified type '{0}' does not implement interface '{1}'", - type.FullName, typeof(TInterface).FullName)); - } - catch(Exception ex) - { - throw new ConfigurationErrorsException( - string.Format("Unable to create instance of type '{0}': {1}", - type.FullName, ex.Message), ex); - } - } #endregion } } \ No newline at end of file diff --git a/TechTalk.SpecFlow.sln b/TechTalk.SpecFlow.sln index a8eaaf993..84db78353 100644 --- a/TechTalk.SpecFlow.sln +++ b/TechTalk.SpecFlow.sln @@ -32,7 +32,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DevenvSetupCustomAction", " EndProject Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "SpecFlowSetup", "Installer\SpecFlowSetup\SpecFlowSetup.vdproj", "{F6740296-282C-4A0F-941E-A8FD1B1DAC2D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechTalk.SpecFlow.GeneratorTools", "GeneratorTools\TechTalk.SpecFlow.GeneratorTools.csproj", "{87BE7FE6-C3DE-4409-ABF6-FA5B60AF3DE1}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechTalk.SpecFlow.Generator", "Generator\TechTalk.SpecFlow.Generator.csproj", "{453D8014-B6CD-4E86-80A8-D59F59092334}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechTalk.SpecFlow.Tools", "Tools\TechTalk.SpecFlow.Tools.csproj", "{87BE7FE6-C3DE-4409-ABF6-FA5B60AF3DE1}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -71,6 +73,10 @@ Global {F6740296-282C-4A0F-941E-A8FD1B1DAC2D}.Debug|Any CPU.ActiveCfg = Debug {F6740296-282C-4A0F-941E-A8FD1B1DAC2D}.Release|Any CPU.ActiveCfg = Release {F6740296-282C-4A0F-941E-A8FD1B1DAC2D}.Release|Any CPU.Build.0 = Release + {453D8014-B6CD-4E86-80A8-D59F59092334}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {453D8014-B6CD-4E86-80A8-D59F59092334}.Debug|Any CPU.Build.0 = Debug|Any CPU + {453D8014-B6CD-4E86-80A8-D59F59092334}.Release|Any CPU.ActiveCfg = Release|Any CPU + {453D8014-B6CD-4E86-80A8-D59F59092334}.Release|Any CPU.Build.0 = Release|Any CPU {87BE7FE6-C3DE-4409-ABF6-FA5B60AF3DE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {87BE7FE6-C3DE-4409-ABF6-FA5B60AF3DE1}.Debug|Any CPU.Build.0 = Debug|Any CPU {87BE7FE6-C3DE-4409-ABF6-FA5B60AF3DE1}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/Tests/ParserTests/ParserTests.csproj b/Tests/ParserTests/ParserTests.csproj index 24f52ceb4..18d0c9cb1 100644 --- a/Tests/ParserTests/ParserTests.csproj +++ b/Tests/ParserTests/ParserTests.csproj @@ -106,6 +106,10 @@ + + {453D8014-B6CD-4E86-80A8-D59F59092334} + TechTalk.SpecFlow.Generator + {7CCEF6D6-FC17-422E-9BED-EDD752B6496F} TechTalk.SpecFlow.Parser diff --git a/Tests/ParserTests/SuccessfulGenerationTest.cs b/Tests/ParserTests/SuccessfulGenerationTest.cs index 80ae49e83..0fdadb661 100644 --- a/Tests/ParserTests/SuccessfulGenerationTest.cs +++ b/Tests/ParserTests/SuccessfulGenerationTest.cs @@ -9,6 +9,8 @@ using System.Xml.Serialization; using Microsoft.CSharp; using NUnit.Framework; +using TechTalk.SpecFlow.Generator; +using TechTalk.SpecFlow.Generator.UnitTestProvider; using TechTalk.SpecFlow.Parser; using TechTalk.SpecFlow.Parser.SyntaxElements; @@ -156,11 +158,11 @@ private void CompareWithExpectedResult(Feature feature, string expectedResultFil private void GenerateCodeFromFeature(Feature feature, TextWriter writer) { SpecFlowUnitTestConverter converter = new SpecFlowUnitTestConverter(new NUnitTestConverter(), true); - var compileUnit = converter.GenerateUnitTestFixture(feature, "TestClassName", "Target.Namespace"); + var codeNamespace = converter.GenerateUnitTestFixture(feature, "TestClassName", "Target.Namespace"); CSharpCodeProvider codeProvider = new CSharpCodeProvider(); CodeGeneratorOptions options = new CodeGeneratorOptions(); - codeProvider.GenerateCodeFromCompileUnit(compileUnit, writer, options); + codeProvider.GenerateCodeFromNamespace(codeNamespace, writer, options); } private void GenerateCodeFromFeature(Feature feature, string fileName) diff --git a/Tests/RuntimeTests/ExecutionTestBase.cs b/Tests/RuntimeTests/ExecutionTestBase.cs index 3faf8a739..f86220a18 100644 --- a/Tests/RuntimeTests/ExecutionTestBase.cs +++ b/Tests/RuntimeTests/ExecutionTestBase.cs @@ -1,4 +1,5 @@ using System; +using System.CodeDom; using System.CodeDom.Compiler; using System.Collections.Generic; using System.Globalization; @@ -7,6 +8,8 @@ using Microsoft.CSharp; using NUnit.Framework; using ParserTests; +using TechTalk.SpecFlow.Generator; +using TechTalk.SpecFlow.Generator.UnitTestProvider; using TechTalk.SpecFlow.Parser; using TechTalk.SpecFlow.Parser.SyntaxElements; @@ -171,7 +174,9 @@ private object CompileAndCreateTest(string fileName, Feature feature) string className = Path.GetFileNameWithoutExtension(fileName); const string targetNamespace = "Target.Namespace"; SpecFlowUnitTestConverter converter = new SpecFlowUnitTestConverter(new NUnitTestConverter(), true); - var compileUnit = converter.GenerateUnitTestFixture(feature, className, targetNamespace); + var codeNamespace = converter.GenerateUnitTestFixture(feature, className, targetNamespace); + var compileUnit = new CodeCompileUnit(); + compileUnit.Namespaces.Add(codeNamespace); Dictionary providerOptions = new Dictionary(); providerOptions["CompilerVersion"] = "v3.5"; @@ -197,4 +202,4 @@ private object CompileAndCreateTest(string fileName, Feature feature) return Activator.CreateInstance(testType); } } -} \ No newline at end of file +} diff --git a/Tests/RuntimeTests/RuntimeTests.csproj b/Tests/RuntimeTests/RuntimeTests.csproj index 8b138177f..5c9158ec6 100644 --- a/Tests/RuntimeTests/RuntimeTests.csproj +++ b/Tests/RuntimeTests/RuntimeTests.csproj @@ -73,6 +73,10 @@ + + {453D8014-B6CD-4E86-80A8-D59F59092334} + TechTalk.SpecFlow.Generator + {70376361-0BE1-478D-8EEC-47BD1C768165} ParserTests diff --git a/Tools/MsBuild/GeneratorTask.cs b/Tools/MsBuild/GeneratorTask.cs new file mode 100644 index 000000000..e55e8077e --- /dev/null +++ b/Tools/MsBuild/GeneratorTask.cs @@ -0,0 +1,66 @@ +using System; +using System.CodeDom.Compiler; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Microsoft.Build.Framework; +using TechTalk.SpecFlow.Generator; +using TechTalk.SpecFlow.Generator.Configuration; + +namespace TechTalk.SpecFlow.Tools.MsBuild +{ + internal class MsBuildBatchGenerator : BatchGenerator + { + private readonly GeneratorTaskBase task; + + public MsBuildBatchGenerator(TextWriter traceWriter, bool verboseOutput, GeneratorTaskBase task) : base(traceWriter, verboseOutput) + { + this.task = task; + } + + private GeneratorTaskBase.OutputFile outputFile = null; + + protected override StreamWriter GetWriter(string codeFileFullPath) + { + outputFile = task.PrepareOutputFile(codeFileFullPath); + + return base.GetWriter(outputFile.FilePathForWriting); + } + + protected override void GenerateFile(SpecFlowGenerator generator, SpecFlowFeatureFile featureFile, string codeFileFullPath) + { + try + { + base.GenerateFile(generator, featureFile, codeFileFullPath); + outputFile.Done(task.Errors); + } + catch(Exception ex) + { + task.RecordException(ex); + } + finally + { + outputFile = null; + } + } + } + + public class GeneratorTask : GeneratorTaskBase + { + public bool VerboseOutput { get; set; } + public bool ForceGeneration { get; set; } + + [Required] + public string ProjectPath { get; set; } + + protected override void DoExecute() + { + SpecFlowProject specFlowProject = MsBuildProjectReader.LoadSpecFlowProjectFromMsBuild(ProjectPath); + + BatchGenerator batchGenerator = new MsBuildBatchGenerator( + GetMessageWriter(MessageImportance.High), VerboseOutput, this); + batchGenerator.ProcessProject(specFlowProject, ForceGeneration); + } + } +} \ No newline at end of file diff --git a/Tools/MsBuild/GeneratorTaskBase.cs b/Tools/MsBuild/GeneratorTaskBase.cs new file mode 100644 index 000000000..7e78dea40 --- /dev/null +++ b/Tools/MsBuild/GeneratorTaskBase.cs @@ -0,0 +1,203 @@ +using System; +using System.CodeDom.Compiler; +using System.Diagnostics; +using System.IO; +using System.Linq; + +namespace TechTalk.SpecFlow.Tools.MsBuild +{ + public abstract class GeneratorTaskBase : TaskBase + { + public bool BuildServerMode { get; set; } + public bool OverwriteReadOnlyFiles { get; set; } + public bool OnlyUpdateIfChanged { get; set; } + + public class OutputFile + { + public string FilePath { get; private set; } + + public virtual string FilePathForWriting + { + get { return FilePath; } + } + + public OutputFile(string filePath) + { + FilePath = filePath; + } + + public virtual void Done(CompilerErrorCollection result) + { + // nop; + } + } + + public abstract class TempOutputFile : OutputFile + { + public string TempFilePath { get; private set; } + + public override string FilePathForWriting + { + get { return TempFilePath; } + } + + protected TempOutputFile(string filePath) + : base(filePath) + { + TempFilePath = Path.Combine(Path.GetTempPath(), "tmp" + Path.GetFileName(filePath)); + } + + public override void Done(CompilerErrorCollection result) + { + SafeDeleteFile(TempFilePath); + } + } + + public class VerifyDifferenceOutputFile : TempOutputFile + { + public VerifyDifferenceOutputFile(string filePath) : base(filePath) + { + } + + public override void Done(CompilerErrorCollection result) + { + if (!FileCompare(TempFilePath, FilePath)) + { + string message = String.Format("Error during file generation. The target file '{0}' is read-only, but different from the transformation result. This problem can be a sign of an inconsistent source code package. Compile and check-in the current version of the file from the development environment or remove the read-only flag from the generation result. To compile a solution that contains messaging project on a build server, you can also exclude the messaging project from the build-server solution or set the msbuild project parameter to 'true' in the messaging project file.", + Path.GetFullPath(FilePath)); + result.Add(new CompilerError(String.Empty, 0, 0, null, message)); + } + + base.Done(result); + } + } + + public class UpdateIfChangedOutputFile : TempOutputFile + { + public UpdateIfChangedOutputFile(string filePath) : base(filePath) + { + } + + public override void Done(CompilerErrorCollection result) + { + if (!FileCompare(TempFilePath, FilePath)) + { + ReplaceFile(TempFilePath, FilePath); + } + + base.Done(result); + } + } + + public OutputFile PrepareOutputFile(string outputFilePath) + { + if (OverwriteReadOnlyFiles) + { + RemoveReadOnly(outputFilePath); + } + + bool isReadOnly = IsReadOnly(outputFilePath); + if (isReadOnly && BuildServerMode) + return new VerifyDifferenceOutputFile(outputFilePath); + + if (OnlyUpdateIfChanged) + return new UpdateIfChangedOutputFile(outputFilePath); + + return new OutputFile(outputFilePath); + } + + // This method accepts two strings the represent two files to + // compare. A return value of true indicates that the contents of the files + // are the same. A return value of any other value indicates that the + // files are not the same. + private static bool FileCompare(string filePath1, string filePath2) + { + int file1byte; + int file2byte; + + // Determine if the same file was referenced two times. + if (string.Equals(filePath1, filePath2, StringComparison.CurrentCultureIgnoreCase)) + { + // Return true to indicate that the files are the same. + return true; + } + + // Open the two files. + using (FileStream fs1 = new FileStream(filePath1, FileMode.Open, FileAccess.Read)) + using (FileStream fs2 = new FileStream(filePath2, FileMode.Open, FileAccess.Read)) + { + // Check the file sizes. If they are not the same, the files + // are not the same. + if (fs1.Length != fs2.Length) + { + // Return false to indicate files are different + return false; + } + + // Read and compare a byte from each file until either a + // non-matching set of bytes is found or until the end of + // file1 is reached. + do + { + // Read one byte from each file. + file1byte = fs1.ReadByte(); + file2byte = fs2.ReadByte(); + } while ((file1byte == file2byte) && (file1byte != -1)); + } + + // Return the success of the comparison. "file1byte" is + // equal to "file2byte" at this point only if the files are + // the same. + return ((file1byte - file2byte) == 0); + } + + private static bool IsReadOnly(string path) + { + try + { + FileInfo fileInfo = new FileInfo(path); + if (!fileInfo.Exists) + return false; + return fileInfo.IsReadOnly; + + } + catch (Exception ex) + { + Debug.WriteLine(ex, "IsReadOnly"); + + // if there is an exception, we let the generation to discover the real problem + return false; + } + } + + private static void RemoveReadOnly(string path) + { + FileInfo fileInfo = new FileInfo(path); + if (fileInfo.Exists && fileInfo.IsReadOnly) + fileInfo.IsReadOnly = false; + } + + private static void SafeDeleteFile(string path) + { + try + { + if (IsReadOnly(path)) + RemoveReadOnly(path); + File.Delete(path); + + } + catch (Exception ex) + { + Debug.WriteLine(ex, "SaveDeleteFile"); + } + } + + private static void ReplaceFile(string sourcePath, string targetPath) + { + if (File.Exists(targetPath)) + SafeDeleteFile(targetPath); + + File.Move(sourcePath, targetPath); + } + } +} \ No newline at end of file diff --git a/Tools/MsBuild/TaskBase.cs b/Tools/MsBuild/TaskBase.cs new file mode 100644 index 000000000..70b0b2352 --- /dev/null +++ b/Tools/MsBuild/TaskBase.cs @@ -0,0 +1,129 @@ +using System; +using System.CodeDom.Compiler; +using System.IO; +using System.Linq; +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace TechTalk.SpecFlow.Tools.MsBuild +{ + public abstract class TaskBase : Task + { + public bool ShowTrace { get; set;} + + protected internal CompilerErrorCollection Errors { get; private set; } + + public override bool Execute() + { + Errors = new CompilerErrorCollection(); + try + { + DoExecute(); + } + catch (Exception ex) + { + RecordException(ex); + } + + + // handle errors + if (Errors.Count > 0) + { + bool hasErrors = false; + foreach (CompilerError error in Errors) + { + if (error.IsWarning) + OutputWarning(error.ToString(), error.ErrorText, error.FileName, error.Line, error.Column); + else + { + OutputError(error.ToString(), error.ErrorText, error.FileName, error.Line, error.Column); + hasErrors = true; + } + } + + return !hasErrors; + } + + return true; + } + + public void RecordException(Exception ex) + { + string message = ex.Message; + if (ShowTrace) + message += Environment.NewLine + ex; + Errors.Add(new CompilerError(String.Empty, 0, 0, null, message)); + } + + protected void OutputError(string outString, string message, string fileName, int lineNumber, int columnNumber) + { + message = message.TrimEnd('\n', '\r'); + + Log.LogError(null, null, null, fileName, lineNumber, columnNumber, 0, 0, message); + } + + protected void OutputWarning(string outString, string message, string fileName, int lineNumber, int columnNumber) + { + message = message.TrimEnd('\n', '\r'); + + Log.LogWarning(null, null, null, fileName, lineNumber, columnNumber, 0, 0, message); + } + + protected void OutputInformation(MessageImportance importance, string message, params object[] messageArgs) + { + message = message.TrimEnd('\n', '\r'); + + Log.LogMessage(importance, message, messageArgs); + } + + private class MessageTextWriter : TextWriter + { + private TaskBase task; + private MessageImportance importance = MessageImportance.Normal; + + public MessageTextWriter(TaskBase task) + { + this.task = task; + } + + public MessageTextWriter(TaskBase task, MessageImportance importance) + { + this.task = task; + this.importance = importance; + } + + public override Encoding Encoding + { + get { return Encoding.Unicode; } + } + + public override void Write(char value) + { + Write(value.ToString()); + } + + public override void Write(char[] buffer, int index, int count) + { + Write(new string(buffer, index, count)); + } + + public override void Write(string value) + { + task.OutputInformation(importance, value); + } + } + + protected TextWriter GetMessageWriter() + { + return new MessageTextWriter(this); + } + + protected TextWriter GetMessageWriter(MessageImportance importance) + { + return new MessageTextWriter(this, importance); + } + + protected abstract void DoExecute(); + } +} \ No newline at end of file diff --git a/Tools/MsBuild/TechTalk.SpecFlow.targets b/Tools/MsBuild/TechTalk.SpecFlow.targets new file mode 100644 index 000000000..6f19eebe0 --- /dev/null +++ b/Tools/MsBuild/TechTalk.SpecFlow.targets @@ -0,0 +1,58 @@ + + + + + + + + false + + + + false + + false + false + false + + + + false + true + + + + + UpdateFeatureFilesInProject; + $(BuildDependsOn) + + + SwitchToForceGenerate; + $(RebuildDependsOn) + + + + + + true + true + + + + + + + + + diff --git a/Tools/MsBuild/TechTalk.SpecFlow.tasks b/Tools/MsBuild/TechTalk.SpecFlow.tasks new file mode 100644 index 000000000..0e74b9cbf --- /dev/null +++ b/Tools/MsBuild/TechTalk.SpecFlow.tasks @@ -0,0 +1,17 @@ + + + + specflow.exe + + + + + <__SpecFlowTasksFullPath>$(SpecFlowTasksPath) + + <__SpecFlowTasksFullPath Condition="Exists('$(MSBuildProjectDirectory)\$(SpecFlowTasksPath)')" + >$(MSBuildProjectDirectory)\$(SpecFlowTasksPath) + + + + + diff --git a/Tools/Program.cs b/Tools/Program.cs new file mode 100644 index 000000000..f7f98fe0d --- /dev/null +++ b/Tools/Program.cs @@ -0,0 +1,64 @@ +using System; +using System.IO; +using NConsoler; +using TechTalk.SpecFlow.Generator; +using TechTalk.SpecFlow.Generator.Configuration; +using TechTalk.SpecFlow.Reporting.NUnitExecutionReport; +using TechTalk.SpecFlow.Reporting.StepDefinitionReport; + +namespace TechTalk.SpecFlow.Tools +{ + class Program + { + static void Main(string[] args) + { + Consolery.Run(typeof(Program), args); + return; + } + + [Action("Generate tests from all feature files in a project")] + public static void GenerateAll( + [Required] string projectFile, + [Optional(false, "force", "f")] bool forceGeneration, + [Optional(false, "verbose", "v")] bool verboseOutput + ) + { + SpecFlowProject specFlowProject = MsBuildProjectReader.LoadSpecFlowProjectFromMsBuild(projectFile); + + BatchGenerator batchGenerator = new BatchGenerator(Console.Out, verboseOutput); + batchGenerator.ProcessProject(specFlowProject, forceGeneration); + } + + #region Reports + + [Action("Generates a report about usage and binding of steps")] + public static void StepDefinitionReport( + [Required] string projectFile, + [Optional("bin\\Debug")] string binFolder, + [Optional("StepDefinitionReport.html", "out")] string outputFile + ) + { + StepDefinitionReportGenerator generator = new StepDefinitionReportGenerator(projectFile, binFolder, true); + generator.GenerateReport(); + generator.TransformReport(Path.GetFullPath(outputFile)); + } + + [Action("Formats an NUnit execution report to SpecFlow style")] + public static void NUnitExecutionReport( + [Required] string projectFile, + [Optional("TestResult.xml")] string xmlTestResult, + [Optional("TestResult.txt", "testOutput")] string labeledTestOutput, + [Optional("TestResult.html", "out")] string outputFile + ) + { + NUnitExecutionReportGenerator generator = new NUnitExecutionReportGenerator( + projectFile, + Path.GetFullPath(xmlTestResult), + Path.GetFullPath(labeledTestOutput)); + generator.GenerateReport(); + generator.TransformReport(Path.GetFullPath(outputFile)); + } + + #endregion + } +} \ No newline at end of file diff --git a/GeneratorTools/Properties/AssemblyInfo.cs b/Tools/Properties/AssemblyInfo.cs similarity index 100% rename from GeneratorTools/Properties/AssemblyInfo.cs rename to Tools/Properties/AssemblyInfo.cs diff --git a/GeneratorTools/TechTalk.SpecFlow.GeneratorTools.csproj b/Tools/TechTalk.SpecFlow.Tools.csproj similarity index 72% rename from GeneratorTools/TechTalk.SpecFlow.GeneratorTools.csproj rename to Tools/TechTalk.SpecFlow.Tools.csproj index d53510c6f..308579ce3 100644 --- a/GeneratorTools/TechTalk.SpecFlow.GeneratorTools.csproj +++ b/Tools/TechTalk.SpecFlow.Tools.csproj @@ -8,8 +8,8 @@ {87BE7FE6-C3DE-4409-ABF6-FA5B60AF3DE1} Exe Properties - TechTalk.SpecFlow.GeneratorTools - TechTalk.SpecFlow.Generator + TechTalk.SpecFlow.Tools + specflow v3.5 512 @@ -33,9 +33,9 @@ 4 - - False - ..\lib\nunit\nunit.framework.dll + + + 3.5 @@ -51,11 +51,20 @@ - - + + NConsoler\NConsoler.cs + + + + + + + {453D8014-B6CD-4E86-80A8-D59F59092334} + TechTalk.SpecFlow.Generator + {7CCEF6D6-FC17-422E-9BED-EDD752B6496F} TechTalk.SpecFlow.Parser @@ -64,10 +73,14 @@ {FC43509F-E7D3-40C4-B4C3-1E6C9D5530A4} TechTalk.SpecFlow.Reporting - - {413EE28C-4F89-4C6F-BA1E-2CDEE4CD43B4} - TechTalk.SpecFlow - + + + + PreserveNewest + + + PreserveNewest +