<|start_filename|>XmlTestAdapter/IFileReader.cs<|end_filename|>
﻿using System;

namespace tSQLtTestAdapter
{
    public interface IFileReader
    {
        DateTime GetLastWriteTimeUtc(string path);
        string ReadAll(string path);
    }
}

<|start_filename|>Test/UnitTests/tSQLtTestFinderTests.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AgileSQLClub.tSQLtTestController;
using Microsoft.SqlServer.TransactSql.ScriptDom;
using NUnit.Framework;
using NUnit.Framework.Internal;

namespace UnitTests
{
    [TestFixture]
    public class tSQLtTestFinderTests
    {
        private readonly TSqlParser _parser = new TSql140Parser(true);

        
        [Test]
        [TestCase(TSqlParserBuilder.SqlServerVersion.Sql90)]
        public void Finds_Extended_Event_Procedure(TSqlParserBuilder.SqlServerVersion version)
        {

            var parser = TSqlParserBuilder.BuildNew(version, false);

            var script = @"EXECUTE sp_addextendedproperty
                            @name = N'tSQLt.TestClass'
                            , @value = 1
                            , @level0type = N'SCHEMA'
                            , @level0name = N'MyUnitSchema';";

            var scanner = new FileScanner(parser);
            scanner.ScanCode(script, new ScanResults(), "path");
        }
        [Test]

        public void Finds_Extended_Event_Procedure_With_Exec()
        {
            var script = @"EXEC sp_addextendedproperty
                            @name = N'tSQLt.TestClass'
                            , @value = 1
                            , @level0type = N'SCHEMA'
                            , @level0name = N'MyUnitSchema';";

            var scanner = new FileScanner(_parser);
            scanner.ScanCode(script, new ScanResults(), "path");
        }


        [Test]
        public void Finds_Extended_Event_Procedure_In_Brackets()
        {
            var script = @"EXECUTE [sp_aDDextendedproperty]
                            @name = N'tSQLt.TestClass'
                            , @value = 1
                            , @level0type = N'SCHEMA'
                            , @level0name = N'MyUnitSchema';";


        }

        [Test]
        public void Finds_Classes_Registered_With_NewTestClass()
        {
            var script = @"tSQLt.NewTestClass @ClassName = 'class name'";

            var scanner = new FileScanner(_parser);
            var results = scanner.ScanCode(script, new ScanResults(), "path");
            Assert.True(results.FoundClasses.Any(p=>p.Name == "class name"));
            Assert.True(results.FoundProperties.Any(p => p.SchemaName == "class name"));
        }

    }
}


<|start_filename|>AgileSQLClub.tSQLtTestController/DacStringExtensions.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.SqlServer.Dac.Model;
using Microsoft.SqlServer.TransactSql.ScriptDom;

namespace AgileSQLClub.tSQLtTestController
{
        public static class ObjectIdentifierExtensions
        {
            public static string GetName(this ObjectIdentifier name)
            {
                return name.Parts.LastOrDefault();
            }

            public static string GetSchema(this ObjectIdentifier name)
            {
                if (name.Parts.Count == 3)
                {
                    return name.Parts[1];
                }

                if (name.Parts.Count == 2)
                {
                    return name.Parts[0];
                }

                return null;
            }

            public static string GetNameFullQuoted(this ObjectIdentifier name)
            {
                var builder = new StringBuilder();
                var first = true;

                foreach (var part in name.Parts.Reverse())
                {
                    builder.Append(part.Quote());
                    if (first)
                    {
                        first = false;
                    }
                    else
                    {
                        builder.Append(".");
                    }
                }

                return builder.ToString();
            }

            public static string GetNameFullUnQuoted(this ObjectIdentifier name)
            {
                var builder = new StringBuilder();
                var first = true;

                foreach (var part in name.Parts) //.Reverse())
                {
                    builder.AppendFormat("{0}.", part.UnQuote());
                }

                var fullName = builder.ToString();
                return fullName.Substring(0, fullName.Length - 1);
            }
            public static string GetSchemaObjectName(this ObjectIdentifier name)
            {
                return string.Format("{0}.{1}", name.GetSchema(), name.GetName());
            }

        
            public static bool EqualsName(this SchemaObjectName source, SchemaObjectName target)
            {
                return source.BaseIdentifier.Quote() == target.BaseIdentifier.Quote() &&
                       source.SchemaIdentifier.Quote() == target.SchemaIdentifier.Quote();
            }

            public static Identifier ToIdentifier(this ObjectIdentifier source)
            {
                var name = source.GetName();

                return new Identifier
                {
                    Value = name.Quote()
                };
            }

            public static SchemaObjectName ToSchemaObjectName(this ObjectIdentifier source)
            {
                var target = new SchemaObjectName();
                target.Identifiers.Add(source.GetSchema().ToScriptDomIdentifier().Quote());
                target.Identifiers.Add(source.GetName().ToScriptDomIdentifier().Quote());

                return target;
            }
        }

        public static class StringExtensions
        {
            public static Identifier ToScriptDomIdentifier(this string source)
            {
                return new Identifier
                {
                    Value = source
                };
            }

            public static bool IsQuoted(this string source)
            {
                return source.StartsWith("[");
            }

            public static string CorrectQuote(this string source, QuoteType type)
            {
                switch (type)
                {
                    case QuoteType.NotQuoted:
                        return source.UnQuote();
                    case QuoteType.SquareBracket:
                        return source.Quote();
                    case QuoteType.DoubleQuote:
                        return source.SpeechQuote();
                    default:
                        throw new ArgumentOutOfRangeException(nameof(type), type, null);
                }
            }
            public static string SpeechQuote(this string source)
            {
                if (!source.StartsWith("\""))
                    source = "\"" + source;

                if (!source.EndsWith("\""))
                    source = source + "\"";

                return source;
            }

            public static string Quote(this string source)
            {
                if (!source.StartsWith("["))
                    source = "[" + source;

                if (!source.EndsWith("]"))
                    source = source + "]";

                return source;
            }

            public static string UnQuote(this string source)
            {
                if (source.StartsWith("["))
                    source = source.Substring(1);

                if (source.EndsWith("]"))
                    source = source.Substring(0, source.Length - 1);

                return source;
            }
        }

        public static class IdentifierExtensions
        {
            public static Identifier Quote(this Identifier src)
            {
                src.Value = src.Value.Quote();
                return src;
            }
        }

        public static class MultiPartIdentifierExtensions
        {
            public static string ToNameString(this MultiPartIdentifier name)
            {
                var builder = new StringBuilder();
                var first = true;

                foreach (var part in name.Identifiers)
                {
                    builder.AppendFormat(".{0}", part.Value.UnQuote());

                }
                if (builder.Length > 0)
                {
                    var ret = builder.ToString();
                    return ret.Substring(1);
                }

                return String.Empty;
            }
        }

        public static class SchemaObjectNameExtensions
        {
            public static ObjectIdentifier ToObjectIdentifier(this SchemaObjectName source)
            {
                return new ObjectIdentifier(source.SchemaIdentifier.Value, source.BaseIdentifier.Value);
            }

            public static Identifier ToIdentifier(this SchemaObjectName source)
            {
                return source.BaseIdentifier;
            }

            public static string ToQuotedString(this SchemaObjectName source)
            {
                if (source.SchemaIdentifier != null)
                {
                    return string.Format("{0}.{1}", source.SchemaIdentifier.Value.Quote(), source.BaseIdentifier.Value.Quote());
                }

                return source.BaseIdentifier.Value.Quote();
            }

            public static string ToUnquotedString(this SchemaObjectName source)
            {
                if (source.SchemaIdentifier != null)
                {
                    return string.Format("{0}.{1}", source.SchemaIdentifier.Value.UnQuote(), source.BaseIdentifier.Value.UnQuote());
                }

                return source.BaseIdentifier.Value.UnQuote();
            }

            public static bool EqualsObjectIdentifierInsensitive(this ObjectIdentifier destination, SchemaObjectName source)
            {
                if (destination.Parts.Count == 1)
                {
                    return destination.Parts[0].UnQuote().ToLowerInvariant() == source.BaseIdentifier.Value.UnQuote().ToLowerInvariant();
                }

                if (destination.Parts.Count == 2)
                {
                    return destination.Parts[0].UnQuote().ToLowerInvariant() == source.SchemaIdentifier.Value.UnQuote().ToLowerInvariant() && destination.Parts[1].UnQuote().ToLowerInvariant() == source.BaseIdentifier.Value.UnQuote().ToLowerInvariant();
                }

                if (destination.Parts.Count == 3)
                {
                    return destination.Parts[0].UnQuote().ToLowerInvariant() == source.DatabaseIdentifier.Value.UnQuote().ToLowerInvariant() && destination.Parts[1].UnQuote().ToLowerInvariant() == source.SchemaIdentifier.Value.UnQuote().ToLowerInvariant() && destination.Parts[2].UnQuote().ToLowerInvariant() == source.BaseIdentifier.Value.UnQuote().ToLowerInvariant();
                }

                if (destination.Parts.Count == 4)
                {
                    return destination.Parts[0].UnQuote().ToLowerInvariant() == source.ServerIdentifier.Value.UnQuote().ToLowerInvariant() && destination.Parts[1].UnQuote().ToLowerInvariant() == source.DatabaseIdentifier.Value.UnQuote().ToLowerInvariant() && destination.Parts[2].UnQuote().ToLowerInvariant() == source.SchemaIdentifier.Value.UnQuote().ToLowerInvariant() && destination.Parts[3].UnQuote().ToLowerInvariant() == source.BaseIdentifier.Value.UnQuote().ToLowerInvariant();
                }

                return false;
            }

            public static bool EqualsObjectIdentifier(this ObjectIdentifier destination, SchemaObjectName source)
            {
                if (destination.Parts.Count == 1)
                {
                    return destination.Parts[0].UnQuote() == source.BaseIdentifier.Value.UnQuote();
                }

                if (destination.Parts.Count == 2)
                {
                    return destination.Parts[0].UnQuote() == source.SchemaIdentifier.Value.UnQuote() && destination.Parts[1].UnQuote() == source.BaseIdentifier.Value.UnQuote();
                }

                if (destination.Parts.Count == 3)
                {
                    return destination.Parts[0].UnQuote() == source.DatabaseIdentifier.Value.UnQuote() && destination.Parts[1].UnQuote() == source.SchemaIdentifier.Value.UnQuote() && destination.Parts[2].UnQuote() == source.BaseIdentifier.Value.UnQuote();
                }

                if (destination.Parts.Count == 4)
                {
                    return destination.Parts[0].UnQuote() == source.ServerIdentifier.Value.UnQuote() && destination.Parts[1].UnQuote() == source.DatabaseIdentifier.Value.UnQuote() && destination.Parts[2].UnQuote() == source.SchemaIdentifier.Value.UnQuote() && destination.Parts[3].UnQuote() == source.BaseIdentifier.Value.UnQuote();
                }

                return false;
            }
        }

        public static class TableNameComparer
        {
            public static bool AreSame(ObjectIdentifier id, SchemaObjectName son)
            {
                switch (id.Parts.Count)
                {
                    case 4:
                        return id.Parts[0] == son.ServerIdentifier.Value && id.Parts[1] == son.DatabaseIdentifier.Value && id.Parts[2] == son.SchemaIdentifier.Value && id.Parts[3] == son.BaseIdentifier.Value;

                    case 3:
                        return id.Parts[0] == son.DatabaseIdentifier.Value && id.Parts[1] == son.SchemaIdentifier.Value && id.Parts[2] == son.BaseIdentifier.Value;

                    case 2:
                        return id.Parts[0] == son.SchemaIdentifier.Value && id.Parts[1] == son.BaseIdentifier.Value;

                    case 1:
                        return id.Parts[0] == son.BaseIdentifier.Value;
                }

                return false;
            }
        }

        public static class ObjectNameExtensions
        {
            public static Identifier ToIdentifier(this string src)
            {
                var id = new Identifier { Value = src };
                return id;
            }

            public static SchemaObjectName ToSchemaObjectName(this string src)
            {
                var name = new SchemaObjectName();
                name.Identifiers.Add(src.ToIdentifier());
                return name;
            }
        }

        public class NameConversionException : Exception
        {
            public NameConversionException(string message)
            {
            }
        }
}


<|start_filename|>Test/UnitTests/FileScannerTests.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AgileSQLClub.tSQLtTestController;
using Microsoft.SqlServer.TransactSql.ScriptDom;
using NUnit.Framework;

namespace UnitTests
{
    [TestFixture]
    public class FileScannerTests
    {
        [Test]
        public void FindsExtendedProperty()
        {
            var scanner = new FileScanner(new TSql130Parser(false));


            var result = 
            scanner.ScanCode(@"EXECUTE sp_addextendedproperty
 @name = N'tSQLt.TestClass'
 , @value = 1
 , @level0type = N'SCHEMA'
 , @level0name = N'MyUnitSchema';
", new ScanResults(), "path");

            Assert.AreEqual(1, result.FoundProperties.Count);
            Assert.AreEqual("MyUnitSchema", result.FoundProperties.First().SchemaName);

        }

        [Test]
        public void FindsSchema()
        {
            var scanner = new FileScanner(new TSql130Parser(false));


            var result =
            scanner.ScanCode("--select 100\r\nGO\r\ncreate      /*AAAAAAAA*/ schema [my_schema];", new ScanResults(), "path");

            Assert.AreEqual(1, result.FoundClasses.Count);
            Assert.AreEqual("my_schema", result.FoundClasses.First().Name);

        }

        [Test]
        public void FindsTestProcedure()
        {
            var scanner = new FileScanner(new TSql130Parser(false));


            var result =
            scanner.ScanCode("--select 100\r\nGO\r\ncreate    procedure [test hello there] as select 1;", new ScanResults(), "path");

            Assert.AreEqual(1, result.FoundPotentialTests.Count);
            Assert.AreEqual("test hello there", result.FoundPotentialTests.First().Name.Object);

        }

        [Test]
        public void FindsDoesNotFindNonTestProcedure()
        {
            var scanner = new FileScanner(new TSql130Parser(false));


            var result =
            scanner.ScanCode("--select 100\r\nGO\r\ncreate    procedure [blah hello there] as select 1;", new ScanResults(), "path");

            Assert.AreEqual(0, result.FoundPotentialTests.Count);
            

        }

    }
}


<|start_filename|>Test/UnitTests/TestCacheTests.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AgileSQLClub.tSQLtTestController;
using Moq;
using NUnit.Framework;
using tSQLtTestAdapter;

namespace UnitTests
{
    [TestFixture]
    public class TestCacheTests
    {
        [Test]
        public void DiscardsDuplicates()
        {
            var mockScanner = new Mock<FileScanner>();
            
            mockScanner.Setup(p => p.ScanCode(It.IsAny<string>(), It.IsAny<ScanResults>(), It.IsAny<string>())).Returns((
                string a, ScanResults result, string b) =>
            {
                result.FoundClasses.Add(new SqlSchema("Schema", "PathA"));
                result.FoundClasses.Add(new SqlSchema("Schema", "PathB"));
                result.FoundClasses.Add(new SqlSchema("Schema", "PathB"));

                result.FoundPotentialTests.Add(new SqlProcedure(new SqlObjectName() {Object = "test", Schema = "Schema"}, "Patha", 0, 100, 1));
                result.FoundPotentialTests.Add(new SqlProcedure(new SqlObjectName() { Object = "test", Schema = "Schema" }, "Pathb", 0, 100, 1));
                return result;
            });

            var mockFileReader = new Mock<IFileReader>();
            mockFileReader.Setup(p => p.ReadAll(It.IsAny<string>())).Returns("Blah");
            mockFileReader.Setup(p => p.GetLastWriteTimeUtc(It.IsAny<string>())).Returns(DateTime.MaxValue);
            
            var testCache = new TestCache(mockScanner.Object, mockFileReader.Object);
            testCache.AddPath("path");
            Assert.AreEqual(1, testCache.GetTests().Count);
        }
    }
}


<|start_filename|>XmlTestAdapter/Helpers/ITestFilesUpdateWatcher.cs<|end_filename|>
using System;

namespace tSQLtTestAdapter.Helpers
{
    public interface ITestFilesUpdateWatcher
    {
        event EventHandler<TestFileChangedEventArgs> FileChangedEvent;
        void AddWatch(string path);
        void RemoveWatch(string path);
    }
}

<|start_filename|>XmlTestAdapter/Helpers/ISolutionEventsListener.cs<|end_filename|>
using System;

namespace tSQLtTestAdapter.Helpers
{
    public interface ISolutionEventsListener
    {
        /// <summary>
        /// Fires an event when a project is opened/closed/loaded/unloaded
        /// </summary>
        event EventHandler<SolutionEventsListenerEventArgs> SolutionProjectChanged;

        void StartListeningForChanges();
        void StopListeningForChanges();
        event EventHandler SolutionUnloaded;
    }
}

<|start_filename|>XmlTestAdapter/tSQLtTestContainerDiscoverer.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TestWindow.Extensibility;
using tSQLtTestAdapter.Helpers;

/*
 * I *think* I need this because the tests are not in a dll or exe - to enumerate tests in a diffrent file format needs 
 *  this, as per bruno's semi-cryptic comment on "https://blogs.msdn.microsoft.com/bhuvaneshwari/2012/03/13/authoring-a-new-visual-studio-unit-test-adapter/"
 *  
 *  BrunoLM
        August 22, 2013 at 12:43 pm
        For non .exe and .dll you need these interfaces as well ITestContainerDiscoverer and ITestContainer

        Example

        github.com/…/TSTestExtension
 
 */

namespace tSQLtTestAdapter
{
    [Export(typeof(ITestContainerDiscoverer))]
    public class tSQLtTestContainerDiscoverer : ITestContainerDiscoverer
    {
        
   
        public event EventHandler TestContainersUpdated;
        private IServiceProvider serviceProvider;
        private ILogger logger;
        private ISolutionEventsListener solutionListener;
        private ITestFilesUpdateWatcher testFilesUpdateWatcher;
        private ITestFileAddRemoveListener testFilesAddRemoveListener;
        private bool initialContainerSearch;
        private readonly List<ITestContainer> cachedContainers;
        protected string FileExtension { get { return Constants.FileExtension; } }
        public Uri ExecutorUri { get { return new System.Uri(Constants.ExecutorUriString); } }
        public IEnumerable<ITestContainer> TestContainers   {get { return GetTestContainers(); }   }

        [ImportingConstructor]
        public tSQLtTestContainerDiscoverer(
            [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider,
            ILogger logger,
            ISolutionEventsListener solutionListener,
            ITestFilesUpdateWatcher testFilesUpdateWatcher,
            ITestFileAddRemoveListener testFilesAddRemoveListener)
        {
            
            initialContainerSearch = true;
            cachedContainers = new List<ITestContainer>();
            this.serviceProvider = serviceProvider;
            this.logger = logger;
            this.solutionListener = solutionListener;
            this.testFilesUpdateWatcher = testFilesUpdateWatcher;
            this.testFilesAddRemoveListener = testFilesAddRemoveListener;

            testFilesUpdateWatcher.FileChangedEvent += OnProjectItemChanged;
            this.testFilesAddRemoveListener.TestFileChanged += OnProjectItemChanged;
            this.testFilesAddRemoveListener.StartListeningForTestFileChanges();

            this.solutionListener.SolutionUnloaded += SolutionListenerOnSolutionUnloaded;
            this.solutionListener.SolutionProjectChanged += OnSolutionProjectChanged;
            this.solutionListener.StartListeningForChanges();
            
        }

        private void OnTestContainersChanged()
        {
            if (TestContainersUpdated != null && !initialContainerSearch)
            {
                TestContainersUpdated(this, EventArgs.Empty);
            }
        }

        private void SolutionListenerOnSolutionUnloaded(object sender, EventArgs eventArgs)
        {
            initialContainerSearch = true;
        }

        private void OnSolutionProjectChanged(object sender, SolutionEventsListenerEventArgs e)
        {
            if (e != null)
            {
                var files = FindSqlFiles(e.Project);
                if (e.ChangedReason == SolutionChangedReason.Load)
                {
                    UpdateFileWatcher(files, true);
                }
                else if (e.ChangedReason == SolutionChangedReason.Unload)
                {
                    UpdateFileWatcher(files, false);
                }
            }

            // Do not fire OnTestContainersChanged here.
            // This will cause us to fire this event too early before the UTE is ready to process containers and will result in an exception.
            // The UTE will query all the TestContainerDiscoverers once the solution is loaded.
        }

        private void UpdateFileWatcher(IEnumerable<string> files, bool isAdd)
        {
            foreach (var file in files)
            {
                if (isAdd)
                {
                    testFilesUpdateWatcher.AddWatch(file);
                    AddTestContainerIfTestFile(file);
                }
                else
                {
                    testFilesUpdateWatcher.RemoveWatch(file);
                    RemoveTestContainer(file);
                }
            }
        }


        private void OnProjectItemChanged(object sender, TestFileChangedEventArgs e)
        {
            if (e != null)
            {
                // Don't do anything for files we are sure can't be test files
                if (!IsSqlFile(e.File)) return;

                switch (e.ChangedReason)
                {
                    case TestFileChangedReason.Added:
                        testFilesUpdateWatcher.AddWatch(e.File);
                        AddTestContainerIfTestFile(e.File);

                        break;
                    case TestFileChangedReason.Removed:
                        testFilesUpdateWatcher.RemoveWatch(e.File);
                        RemoveTestContainer(e.File);

                        break;
                    case TestFileChangedReason.Changed:
                        AddTestContainerIfTestFile(e.File);
                        break;
                }

                OnTestContainersChanged();
            }
        }

        private void AddTestContainerIfTestFile(string file)
        {
            var isTestFile = IsTestFile(file);
            RemoveTestContainer(file); // Remove if there is an existing container

            // If this is a test file
            if (isTestFile)
            {
                var container = new tSQLtTestContainer(this, file, ExecutorUri);
                cachedContainers.Add(container);
            }
        }

        private void RemoveTestContainer(string file)
        {
            var index = cachedContainers.FindIndex(x => x.Source.Equals(file, StringComparison.OrdinalIgnoreCase));
            if (index >= 0)
            {
                cachedContainers.RemoveAt(index);
            }
        }

        private IEnumerable<ITestContainer> GetTestContainers()
        {
            if (initialContainerSearch)
            {
                cachedContainers.Clear();
                var xmlFiles = FindSqlFiles();
                UpdateFileWatcher(xmlFiles, true);
                initialContainerSearch = false;
            }

            return cachedContainers;
        }

        private IEnumerable<string> FindSqlFiles()
        {
            var solution = (IVsSolution)serviceProvider.GetService(typeof(SVsSolution));
            var loadedProjects = solution.EnumerateLoadedProjects(__VSENUMPROJFLAGS.EPF_LOADEDINSOLUTION).OfType<IVsProject>();

            return loadedProjects.SelectMany(FindSqlFiles).ToList();
        }

        private IEnumerable<string> FindSqlFiles(IVsProject project)
        {
            return from item in VsSolutionHelper.GetProjectItems(project)
                   where IsTestFile(item)
                   select item;
        }

        private static bool IsSqlFile(string path)
        {
            return ".sql".Equals(Path.GetExtension(path), StringComparison.OrdinalIgnoreCase);
        }

        private bool IsTestFile(string path)
        {
            try
            {
                if (String.IsNullOrEmpty(path))
                    return false;

                return IsSqlFile(path);
            }
            catch (IOException e)
            {
                logger.Log(MessageLevel.Error, "IO error when detecting a test file during Test Container Discovery" + e.Message);
            }

            return false;
        }


        public void Dispose()
        {
            Dispose(true);
            // Use SupressFinalize in case a subclass
            // of this type implements a finalizer.
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (testFilesUpdateWatcher != null)
                {
                    testFilesUpdateWatcher.FileChangedEvent -= OnProjectItemChanged;
                    ((IDisposable)testFilesUpdateWatcher).Dispose();
                    testFilesUpdateWatcher = null;
                }

                if (testFilesAddRemoveListener != null)
                {
                    testFilesAddRemoveListener.TestFileChanged -= OnProjectItemChanged;
                    testFilesAddRemoveListener.StopListeningForTestFileChanges();
                    testFilesAddRemoveListener = null;
                }

                if (solutionListener != null)
                {
                    solutionListener.SolutionProjectChanged -= OnSolutionProjectChanged;
                    solutionListener.StopListeningForChanges();
                    solutionListener = null;
                }
            }
        }


    }
}


<|start_filename|>XmlTestAdapter/Helpers/RunSettings.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;


namespace tSQLtTestAdapter.Helpers
{
    class RunSettings
    {
        private readonly XDocument _document;

        public RunSettings(IRunSettings runSettings)
        {

            if (runSettings == null || runSettings.SettingsXml == null || String.IsNullOrEmpty(runSettings.SettingsXml))
            {
                return;
            }

            _document = XDocument.Parse(runSettings.SettingsXml);
        }

        public string GetSetting(string name)
        {
            if (_document == null)
                return null;

            var current = _document.Element("RunSettings");
            if (current == null)
            {
                return null;
            }

            current = current.Element("TestRunParameters");

            if (current == null)
            {
                return null;
            }

            foreach (var element in current.Elements())
            {
                if (element.HasAttributes && element.Attribute("name") != null && element.Attribute("name").Value == name)
                {
                    if (element.Attribute("value") == null)
                    {
                        return null;

                    }

                    return element.Attribute("value").Value;

                }
            }

            return null;

        }


    }
}


<|start_filename|>XmlTestAdapter/TestCache.cs<|end_filename|>
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using AgileSQLClub.tSQLtTestController;
using Microsoft.SqlServer.TransactSql.ScriptDom;

namespace tSQLtTestAdapter
{
    public class TestCache
    {
        private readonly Dictionary<string, DateTime> _dateCache = new Dictionary<string, DateTime>();
        private bool _haveChanges = true;
        private ScanResults _results = new ScanResults();
        private readonly FileScanner _scanner;
        private readonly List<TestClass> _tests = new List<TestClass>();
        private readonly IFileReader _fileReader;
        public TestCache()
        {
            _scanner = new FileScanner(new TSql130Parser(false));
            _fileReader = new FileReader();
        }

        public TestCache(FileScanner probablyAMock, IFileReader anotherMock)
        {
            _scanner = probablyAMock;
            _fileReader = anotherMock;
        }


        public void AddPath(string path)
        {
            var date = _fileReader.GetLastWriteTimeUtc(path);
            if (!_dateCache.ContainsKey(path) || date <= _dateCache[path])
            {
                _results = _scanner.ScanCode(_fileReader.ReadAll(path), _results, path);
                _haveChanges = true;
            }
        }

        public List<TestClass> GetTests()
        {
            if (!_haveChanges)
            {
                return _tests;
            }
            
            var foundClasses = new List<SqlSchema>();

            foreach (var clazz in 
                _results.FoundClasses.Where(
                    p =>
                        _results.FoundPotentialTests.Any(
                            e => string.Equals(p.Name, e.Name.Schema, StringComparison.OrdinalIgnoreCase))))
            {
                if (foundClasses.All(p => p.Name != clazz.Name))
                {
                    foundClasses.Add(clazz);
                }

            }

            var foundTests =
                _results.FoundPotentialTests.Where(
                    p =>
                        _results.FoundPotentialTests.Any(
                            s => string.Equals(s.Name.Schema, p.Name.Schema, StringComparison.OrdinalIgnoreCase)));

            _tests.Clear();

            foreach (var cls in foundClasses)
            {
                var testClass = new TestClass();
                testClass.Name = cls.Name;
                testClass.Path = cls.Path;
                
                foreach (var test in foundTests.Where(p => string.Equals(p.Name.Schema, cls.Name, StringComparison.OrdinalIgnoreCase)))
                {
                    testClass.Tests.Add(new Test {Name = test.Name.Object, Path = test.Path, Line = test.StartLine});
                }

                if (testClass.Tests.Count > 0)
                    _tests.Add(testClass);
            }

            _results = new ScanResults();
            _haveChanges = false;

            return _tests;
        }
    }

    public class FileReader : IFileReader
    {
        public FileReader()
        {
            
        }


        public virtual string ReadAll(string path)
        {
            return File.ReadAllText(path);
        }

        public virtual DateTime GetLastWriteTimeUtc(string path)
        {
            return File.GetLastWriteTimeUtc(path);
        }
    }
}

<|start_filename|>AgileSQLClub.tSQLtTestController/TSqlParserBuilder.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.SqlServer.TransactSql.ScriptDom;

namespace AgileSQLClub.tSQLtTestController
{
    public static class TSqlParserBuilder
    {
        public static TSqlParser BuildNew(SqlServerVersion version, bool quoted)
        {
            switch (version)
            {
                case SqlServerVersion.Sql90:
                    return new TSql90Parser(quoted);

                case SqlServerVersion.Sql100:
                    return new TSql100Parser(quoted);

                case SqlServerVersion.Sql110:
                    return new TSql110Parser(quoted);

                case SqlServerVersion.Sql120:
                    return new TSql120Parser(quoted);

                case SqlServerVersion.Sql130:
                    return new TSql130Parser(quoted);

                default:
                    throw new ArgumentOutOfRangeException(nameof(version), version, null);
            }
        }

        public enum SqlServerVersion
        {
            Sql90,
            Sql100,
            Sql110,
            Sql120,
            Sql130
        }
    }

}


<|start_filename|>AgileSQLClub.tSQLtTestController/TestClass.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.SqlServer.TransactSql.ScriptDom;

namespace AgileSQLClub.tSQLtTestController
{
    public class FileScanner
    {
        private readonly TSqlParser _parser;

        public FileScanner()
        {
            _parser = new TSql130Parser(true);
        }

        public FileScanner(TSqlParser parser)
        {
            _parser = parser;
        }

        public virtual ScanResults ScanCode(string code, ScanResults results, string path)
        {
            var batches = code.Split(new[] {"\r\nGO\r\n", "\nGO\n"}, StringSplitOptions.None);
            var offset = 0;
            var lineOffset = 0;

            foreach (var batch in batches)
            {
                results = AppendResults(results, batch, path, offset, lineOffset); //this won't be exact, depending which split option is used we will be 4 or 6 chars off... :(
                offset += batch.Length;
                lineOffset = batch.Split('\n').Length;
            }

            return results;
        }

        private ScanResults AppendResults(ScanResults results, string batch, string path, int offset, int lineOffset)
        {
            var reader = new StringReader(batch);
            IList<ParseError> errors;
            var fragment = _parser.Parse(reader, out errors);
            var visitor = new TestVisitor(path, offset, lineOffset);
            fragment.Accept(visitor);

            results.FoundProperties.AddRange(visitor.ExtendedProperties);
            results.FoundClasses.AddRange(visitor.Schemas);
            results.FoundPotentialTests.AddRange(visitor.Procedures);

            return results;
        }
    }

    public class TestVisitor : TSqlFragmentVisitor
    {
        private readonly int _lineOffset;
        private readonly int _offset;
        private readonly string _path;
        public readonly List<tSQLtExtendedProperty> ExtendedProperties = new List<tSQLtExtendedProperty>();
        public readonly List<SqlProcedure> Procedures = new List<SqlProcedure>();
        public readonly List<SqlSchema> Schemas = new List<SqlSchema>();

        public TestVisitor(string path, int offset, int lineOffset)
        {
            _path = path;
            _offset = offset;
            _lineOffset = lineOffset;
        }

        public override void Visit(CreateProcedureStatement proc)
        {
            var name = new SqlObjectName();
            var son = proc.ProcedureReference.Name;
            name.Schema = son.SchemaIdentifier?.Value.UnQuote();
            name.Object = son.BaseIdentifier?.Value.UnQuote();

            if (name.Object.ToLowerInvariant().StartsWith("test"))
                Procedures.Add(new SqlProcedure(name, _path, _offset + proc.StartOffset, proc.FragmentLength, _lineOffset + proc.StartLine));

            base.Visit(proc);
        }

        public override void Visit(CreateSchemaStatement node)
        {
            Schemas.Add(new SqlSchema(node.Name.Value.UnQuote(), _path));

            base.Visit(node);
        }

        public override void Visit(ExecuteStatement exec)
        {
            if (exec.ExecuteSpecification == null)
                return;

            if (!(exec.ExecuteSpecification.ExecutableEntity is ExecutableProcedureReference))
                return;

            var refereced = (ExecutableProcedureReference) exec.ExecuteSpecification.ExecutableEntity;

            if (refereced.ProcedureReference.ProcedureReference != null && refereced.ProcedureReference.ProcedureReference.Name != null && refereced.ProcedureReference.ProcedureReference.Name.BaseIdentifier.Value.IndexOf("sp_addextendedproperty", StringComparison.OrdinalIgnoreCase) > -1)
            {
                var propertyValue =
                    refereced.Parameters.FirstOrDefault(
                        p =>
                            (p.ParameterValue as StringLiteral)?.Value.IndexOf("tSQLt.TestClass",
                                StringComparison.OrdinalIgnoreCase) > -1);

                if (propertyValue == null)
                    return;

                var schemaName = (refereced.Parameters.FirstOrDefault(p => p.Variable?.Name.ToLowerInvariant() == "@level0name")?.ParameterValue as StringLiteral)?.Value;
                if (!string.IsNullOrEmpty(schemaName))
                    ExtendedProperties.Add(new tSQLtExtendedProperty(schemaName.UnQuote()));
            }

            var name = refereced.ProcedureReference.ProcedureReference?.Name;
            if (name == null)
                return;

            if (name.SchemaIdentifier?.Value.ToLowerInvariant() == "tsqlt" && name.BaseIdentifier?.Value.ToLowerInvariant() == "newtestclass")
            {
                var parameterValue = (refereced.Parameters.FirstOrDefault()?.ParameterValue as StringLiteral)?.Value;

                if (!string.IsNullOrEmpty(parameterValue))
                {
                    ExtendedProperties.Add(new tSQLtExtendedProperty(parameterValue.UnQuote()));
                    Schemas.Add(new SqlSchema( parameterValue.UnQuote(),_path));
                }        
            }
            
        }
    }


    public class ScanResults
    {
        public List<SqlSchema> FoundClasses = new List<SqlSchema>();
        public List<SqlProcedure> FoundPotentialTests = new List<SqlProcedure>();
        public List<tSQLtExtendedProperty> FoundProperties = new List<tSQLtExtendedProperty>();
    }

    public class SqlObjectName
    {
        public string Object;
        public string Schema;
    }

    public class SqlProcedure
    {
        public SqlObjectName Name;

        public SqlProcedure(SqlObjectName name, string path, int startPos, int endPos, int startLine)
        {
            Name = name;
            Path = path;
            StartPos = startPos;
            EndPos = endPos;
            StartLine = startLine;
        }

        public string Path { get; set; }
        public int StartPos { get; set; }
        public int EndPos { get; set; }
        public int StartLine { get; set; }
    }

    public class SqlSchema
    {
        public string Name;

        public string Path;

        public SqlSchema(string schemaName, string path)
        {
            Name = schemaName;
            Path = path;
        }
    }

    public class tSQLtExtendedProperty
    {
        public string SchemaName;

        public tSQLtExtendedProperty(string schemaName)
        {
            SchemaName = schemaName;
        }
    }


    public class TestFinder
    {
        private readonly List<string> _filePaths;
        private readonly string _lookupPath;
        private readonly TSqlParser _parser;

        public TestFinder(TSqlParser parser, List<string> filePaths)
        {
            _parser = parser;
            _filePaths = filePaths;
        }

        public List<TestClass> GetTests()
        {
            var classes = new List<TestClass>();
            var results = new ScanResults();

            foreach (var path in _filePaths)
            {
                var scanner = new FileScanner(_parser);
                results = scanner.ScanCode(File.ReadAllText(path), results, path);
            }

            var foundClasses =
                results.FoundClasses.Where(
                    p =>
                        results.FoundPotentialTests.Any(
                            e => string.Equals(p.Name, e.Name.Schema, StringComparison.OrdinalIgnoreCase)));

            var foundTests =
                results.FoundPotentialTests.Where(
                    p =>
                        results.FoundPotentialTests.Any(
                            s => string.Equals(s.Name.Schema, p.Name.Schema, StringComparison.OrdinalIgnoreCase)));

            return classes;
        }
    }

    public class TestClasses
    {
    }

    public class TestClass
    {
        public string Name;
        public string Path;
        public List<Test> Tests = new List<Test>();
    }

    public class Test
    {
        public string Name;
        public string Path;
        public int Line { get; set; }
    }
}

<|start_filename|>XmlTestAdapter/tSQLtTestExecutor.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Xml.Linq;
using System.Xml.XPath;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
using tSQLt.Client.Net;
using tSQLtTestAdapter.Helpers;

namespace tSQLtTestAdapter
{
    [ExtensionUri(Constants.ExecutorUriString)]
    public class tSQLtTestExecutor : ITestExecutor
    {
        public void RunTests(IEnumerable<string> sources, IRunContext runContext, IFrameworkHandle frameworkHandle)
        {
            XmlTestDiscoverer.SetPathFilter(new RunSettings(runContext.RunSettings).GetSetting("IncludePath"), frameworkHandle);
            IEnumerable<TestCase> tests = XmlTestDiscoverer.GetTests(sources, null);
            RunTests(tests, runContext, frameworkHandle);
        }

        public void RunTests(IEnumerable<TestCase> tests, IRunContext runContext, IFrameworkHandle frameworkHandle)
        {
            m_cancelled = false;
            
            var connectionString =new RunSettings(runContext.RunSettings).GetSetting("TestDatabaseConnectionString");
            if (String.IsNullOrEmpty(connectionString))
            {
                frameworkHandle.SendMessage(TestMessageLevel.Error, @"No connection string found. You need to specify a run setting with the name ""TestDatabaseConnectionString"". Create a .runsettings file a sample is: 

<?xml version=""1.0"" encoding=""utf-8""?>
<RunSettings>
  <!-- Parameters used by tests at runtime -->
  <TestRunParameters>
    <Parameter name=""TestDatabaseConnectionString"" value=""server=Servername;initial catalog=UnitTestDatabase;integrated security=sspi"" />
    <!-- If you have a large project then to speed up discovery, use this to limit which .sql files are parsed. If all your tests are in a subfolder called \UnitTests\ or \Our-UnitTests\ then set the value to UnitTests - it is a regex so U.i.T.s.s will also work.
    <!-- <Parameter name=""IncludePath"" value=""RegexToTestsToInclude"" /> -->
  </TestRunParameters>
</RunSettings>

If you are running tests in visual studio choose ""Test-->Test Settings-->Select Test Settings File--> Choose your .runsettings file"", if you are using the command line pass the runsettings files using /Settings:PathTo.runsettings

");
                return;
            }            


            foreach (TestCase test in tests)
            {
                if (m_cancelled)
                    break;
               
                var testResult = new TestResult(test);
                
                var testSession = new tSQLtTestRunner(connectionString);
                var result = Run(testSession, test);

                if (null == result)
                {
                    continue;
                }

                testResult.Outcome = result.Passed() ? TestOutcome.Passed : TestOutcome.Failed;
                testResult.ErrorMessage += result.FailureMessages();

                frameworkHandle.RecordResult(testResult);
            }

        }

        private static TestSuites Run(tSQLtTestRunner testSession, TestCase test)
        {
            if (test != null && test.DisplayName != null && test.DisplayName.Contains("."))
                return testSession.Run(test.DisplayName.Split('.')[0], test.DisplayName.Split('.')[1]);
            return null;
        }


        public void Cancel()
        {
            m_cancelled = true;
        }

        public static readonly Uri ExecutorUri = new Uri(Constants.ExecutorUriString);
        private bool m_cancelled;
    }

    public static class Constants
    {
        public const string ExecutorUriString = "executor://tSQLtTestExecutor/v3";
        public const string FileExtension = ".sql";

    }
}


<|start_filename|>XmlTestAdapter/tSQLtTestDiscoverer.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.VisualStudio.TestPlatform.ObjectModel;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging;
using tSQLtTestAdapter.Helpers;

namespace tSQLtTestAdapter
{
    [DefaultExecutorUri(Constants.ExecutorUriString)]
    [FileExtension(Constants.FileExtension)]
    public class XmlTestDiscoverer : ITestDiscoverer
    {
        private static readonly object _lock = new object();
        private static readonly TestCache _tests = new TestCache();

        private static readonly List<Regex> _includePaths = new List<Regex>();
        

        public void DiscoverTests(IEnumerable<string> sources, IDiscoveryContext discoveryContext, IMessageLogger logger, ITestCaseDiscoverySink discoverySink)
        {
            if (string.IsNullOrEmpty(new RunSettings(discoveryContext.RunSettings).GetSetting("TestDatabaseConnectionString")))
            {
                logger.SendMessage(TestMessageLevel.Informational, "No RunSettings TestDatabaseConnectionString set - will not attempt to discover tests..");
                return;
            }
            
            logger.SendMessage(TestMessageLevel.Informational, "tSQLt Test Adapter, searching for tests...");
            
            var includePath = new RunSettings(discoveryContext.RunSettings).GetSetting("IncludePath");
            SetPathFilter(includePath, logger);

            lock (_lock)
            {
                GetTests(sources, discoverySink);
            }

            if (_tests != null)
                logger.SendMessage(TestMessageLevel.Informational, string.Format("tSQLt Test Adapter, searching for tests...done - {0} found", _tests.GetTests().Sum(p => p.Tests.Count)));
            else
                logger.SendMessage(TestMessageLevel.Informational, "tSQLt Test Adapter, searching for tests...done - none found");
        }

        public static void SetPathFilter(string includePath, IMessageLogger logger)
        {
            try
            {
                _includePaths.Clear();

                if (!string.IsNullOrEmpty(includePath))
                {
                    if (includePath.IndexOf(";", StringComparison.Ordinal) >= 0)
                    {
                        foreach (var part in includePath.Split(';'))
                        {
                            logger.SendMessage(TestMessageLevel.Informational, string.Format("tSQLt Test Adapter, adding filter...- {0}", part));
                            _includePaths.Add(new Regex(part));
                        }
                    }
                    else
                    {
                        logger.SendMessage(TestMessageLevel.Informational, string.Format("tSQLt Test Adapter, adding filter...- {0}", includePath));
                        _includePaths.Add(new Regex(includePath));
                    }
                }
            }catch(Exception e)
            {
                logger.SendMessage(TestMessageLevel.Informational, string.Format("tSQLt Test Adapter, *ERROR* adding filter...- {0}", includePath));
            }
        }

        public static List<TestCase> GetTests(IEnumerable<string> sources, ITestCaseDiscoverySink discoverySink)
        {
            lock (_lock)
            {
                var tests = new List<TestCase>();

                foreach (var source in sources)
                {
                    if (_includePaths.Count == 0 || _includePaths.Any(p => p.IsMatch(source)))
                        _tests.AddPath(source);
                }

                var testInCode = _tests.GetTests();
                
                foreach (var testClass in testInCode)
                {
                    foreach (var test in testClass.Tests)
                    {
                        var testCase = new TestCase(string.Format("tSQLt.{0}.{1}", testClass.Name, test.Name), tSQLtTestExecutor.ExecutorUri, test.Path);
                        

                        testCase.LineNumber = test.Line;
                        testCase.CodeFilePath = test.Path;

                        tests.Add(testCase);

                        if (discoverySink != null)
                        {
                            discoverySink.SendTestCase(testCase);
                        }
                    }

                    if (discoverySink != null)
                    {
                        var tcClass = new TestCase("tSQLt.Test Class." + testClass.Name, tSQLtTestExecutor.ExecutorUri, testClass.Path);

                        tcClass.CodeFilePath = testClass.Path;

                        tests.Add(tcClass);

                        discoverySink.SendTestCase(tcClass);
                    }
                }

                return tests;
            }
        }
    }
}

<|start_filename|>XmlTestAdapter/Helpers/SolutionEventsListener.cs<|end_filename|>
﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;


namespace tSQLtTestAdapter.Helpers
{
  
    [Export(typeof(ISolutionEventsListener))]
    public class SolutionEventsListener : IVsSolutionEvents, ISolutionEventsListener
    {
        private readonly IVsSolution solution;
        private uint cookie = VSConstants.VSCOOKIE_NIL;

        /// <summary>
        /// Fires an event when a project is opened/closed/loaded/unloaded
        /// </summary>
        public event EventHandler<SolutionEventsListenerEventArgs> SolutionProjectChanged;

        public event EventHandler SolutionUnloaded;

        [ImportingConstructor]
        public SolutionEventsListener([Import(typeof(SVsServiceProvider))]IServiceProvider serviceProvider)
        {
            //Has been made internal in 2017 agghhhhh!
//            ValidateArg.NotNull(serviceProvider, "serviceProvider");
            this.solution = serviceProvider.GetService(typeof(SVsSolution)) as IVsSolution;
        }

        public void StartListeningForChanges()
        {
            if (this.solution != null)
            {
                int hr = this.solution.AdviseSolutionEvents(this, out cookie);
                ErrorHandler.ThrowOnFailure(hr); // do nothing if this fails
            }
        }

        public void StopListeningForChanges()
        {
            if (this.cookie != VSConstants.VSCOOKIE_NIL && this.solution != null)
            {
                int hr = this.solution.UnadviseSolutionEvents(cookie);
                ErrorHandler.Succeeded(hr); // do nothing if this fails

                this.cookie = VSConstants.VSCOOKIE_NIL;
            }
        }

        public void OnSolutionProjectUpdated(IVsProject project, SolutionChangedReason reason)
        {
            if (SolutionProjectChanged != null && project != null)
            {
                SolutionProjectChanged(this, new SolutionEventsListenerEventArgs(project, reason));
            }
        }

        public void OnSolutionUnloaded()
        {
            if(SolutionUnloaded != null)
            {
                SolutionUnloaded(this, new System.EventArgs());
            }
        }


        /// <summary>
        /// This event is called when a project has been reloaded. This happens when you choose to unload a project 
        /// (often to edit its .proj file) and then reload it.
        /// </summary>
        public int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy)
        {
            var project = pRealHierarchy as IVsProject;
            OnSolutionProjectUpdated(project, SolutionChangedReason.Load);
            return VSConstants.S_OK;
        }

        /// <summary>
        /// This gets called when a project is unloaded
        /// </summary>
        public int OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy)
        {
            var project = pRealHierarchy as IVsProject;
            OnSolutionProjectUpdated(project, SolutionChangedReason.Unload);
            return VSConstants.S_OK;
        }

	    public int OnAfterCloseSolution(object pUnkReserved)
	    {
	        OnSolutionUnloaded();
            return VSConstants.S_OK;
        }

        // Unused events...

        /// <summary>
        /// This gets called when a project is opened
        /// </summary>
        /// <param name="pHierarchy"></param>
        /// <param name="fAdded">0 if alreay part of solution, 1 if it is being added to the solution</param>
        /// <returns></returns>
        public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded)
        {
            return VSConstants.S_OK;
        }

        public int OnAfterOpenSolution(object pUnkReserved, int fNewSolution)
        {
            return VSConstants.S_OK;
        }

        public int OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved)
        {
            return VSConstants.S_OK;
        }

        public int OnBeforeCloseSolution(object pUnkReserved)
        {
            return VSConstants.S_OK;
        }

        public int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel)
        {
            return VSConstants.S_OK;
        }

        public int OnQueryCloseSolution(object pUnkReserved, ref int pfCancel)
        {
            return VSConstants.S_OK;
        }

        public int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel)
        {
            return VSConstants.S_OK;
        }
    }

}