PSDropNew.Tests/IntelliTect/PSProviderTestBase.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Provider;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
 
namespace IntelliTect.Management.Automation.UnitTesting
{
    public abstract partial class PSProviderTestBase<TProvider, TDriveInfo>
            where TProvider : CmdletProvider
    {
        private static PowerShell _PowerShell;
 
        public static PowerShell PowerShell
        {
            get { return _PowerShell; }
            set
            {
                if ( _PowerShell != null )
                {
                    _PowerShell.Dispose();
                }
                _PowerShell = value;
            }
        }
 
        public virtual TestContext TestContext { get; set; }
        public static TProvider Provider { get; set; }
 
        public static string ProviderName
        {
            get
            {
                CmdletProviderAttribute cmdletProviderAttribute =
                        (CmdletProviderAttribute) typeof(TProvider).GetCustomAttributes(
                                typeof(CmdletProviderAttribute),
                                false ).First();
                return cmdletProviderAttribute.ProviderName;
            }
        }
 
        /// <summary>
        /// Invokes the command within the PowerShell session.
        /// </summary>
        /// <param name="commandFormat">
        /// A command expressed as a composite format string
        /// (see http://msdn.microsoft.com/en-us/library/txafckwd(v=vs.110).aspx) into which the args
        /// can be embedded (e.d. New-Item {0}"
        /// </param>
        /// <param name="args">An object array that contains zero or more objects to format.</param>
        /// <returns></returns>
        public ICollection<PSObject> PowerShellInvoke( string commandFormat, params object[] args )
        {
            commandFormat = string.Format( commandFormat, args );
            return PowerShellInvoke( commandFormat, false, TestContext );
        }
 
        /// <summary>
        /// Invokes the command within the PowerShell session.
        /// </summary>
        /// <param name="ignoreErrors">set to true to ignore errors</param>
        /// <param name="commandFormat">
        /// A command expressed as a composite format string
        /// (see http://msdn.microsoft.com/en-us/library/txafckwd(v=vs.110).aspx) into which the args
        /// can be embedded (e.d. New-Item {0}"
        /// </param>
        /// <param name="args">An object array that contains zero or more objects to format.</param>
        /// <returns></returns>
        public ICollection<PSObject> PowerShellInvoke( bool ignoreErrors, string commandFormat, params object[] args )
        {
            commandFormat = string.Format( commandFormat, args );
            return PowerShellInvoke( commandFormat, ignoreErrors, TestContext );
        }
 
        public static ICollection<PSObject> PowerShellInvoke( string command,
                bool ignoreErrors,
                TestContext testContext = null )
        {
            TestContextWriteLine( testContext, command );
            PowerShell.AddScript( command );
            string errorText = null;
            ICollection<PSObject> results = PowerShell.Invoke();
            //TestContextWriteLine(testContext, "HistoryString: " + PowerShell.HistoryString);
            PowerShell.Commands.Clear();
            foreach ( PSObject item in results )
            {
                TestContextWriteLine( testContext, "{0}", item );
            }
            foreach ( DebugRecord record in PowerShell.Streams.Debug.ReadAll() )
            {
                TestContextWriteLine( testContext, "DEBUG: {0}", record );
            }
            foreach ( VerboseRecord record in PowerShell.Streams.Verbose.ReadAll() )
            {
                TestContextWriteLine( testContext, "VERBOSE: {0}", record );
            }
            foreach ( WarningRecord record in PowerShell.Streams.Warning.ReadAll() )
            {
                TestContextWriteLine( testContext, "WARNING: {0}", record );
            }
            if ( !ignoreErrors )
            {
                // Used to allow caller to read the errors.
                IEnumerable<ErrorRecord> errors = PowerShell.Streams.Error.ReadAll();
                errorText = string.Join( Environment.NewLine, errors );
                foreach ( ErrorRecord record in errors )
                {
                    TestContextWriteLine( testContext, "ERROR: {0}", record );
                }
                Assert.IsFalse( PowerShell.HadErrors, errorText );
            }
 
            return results;
        }
 
        private static void TestContextWriteLine( TestContext testContext, string commandFormat, params object[] args )
        {
            string command;
            if ( args == null ||
                 !args.Any() )
            {
                command = commandFormat;
            }
            else
            {
                command = string.Format( commandFormat, args );
            }
            if ( testContext != null )
            {
                testContext.WriteLine( "Command: {0}", command );
            }
            else
            {
                Console.WriteLine( "Command: {0}", command );
            }
        }
 
        public static TProvider ImportModule( TestContext testContext )
        {
            TProvider provider = null;
            ProviderEventArgs<TProvider>.OnNewInstance += ( sender, eventArgs ) => provider = eventArgs.Provider;
            string psProviderPath = typeof(TProvider).Assembly.Location;
 
            PowerShell.AddCommand( "Set-ExecutionPolicy" )
                    .AddArgument( "Unrestricted" )
                    .AddParameter( "Scope", "Process" );
            PowerShell.Invoke();
 
            string command = @"if(test-path variable:module) {{
                        remove-module $module -Verbose;
                    }};
                     
                    $module=(import-module '{0}' -PassThru -Verbose);
                    #Write-Output $module";
 
            command = string.Format( command, new FileInfo(psProviderPath).Directory.Parent.Parent.Parent.FullName + "\\IntelliTect.PSDropbin.psd1" );
            PowerShellInvoke( command, false, testContext );
 
            Assert.IsNotNull( provider );
            return provider;
        }
 
        public static TDriveInfo NewDrive( string driveName = "DropboxTestDrive", TestContext testContext = null )
        {
            // TODO: Change to dynamically determine New-PSDrive parameters.
            string command = @"if(!(Test-Path Variable:\{0})) {{
                    ${0} = New-PSDrive -PSProvider {1} -Name {0} -Root ""/"" -Verbose
                }}
                Get-PSDrive ${0}";
            command = string.Format( command, driveName, ProviderName );
            TDriveInfo driveInfo =
                    (TDriveInfo) PowerShellInvoke( command, false, testContext ).First().ImmediateBaseObject;
            return driveInfo;
        }
 
        public static bool TestPath( string path, TestContext testContext = null )
        {
            return
                    (bool)
                            PowerShellInvoke( string.Format( "Test-Path {0}", path ), false, testContext )
                                    .Single()
                                    .ImmediateBaseObject;
        }
 
        // TODO: Change to not use dynamic
        public static dynamic NewItem( string path, TestContext testContext = null )
        {
            string itemType = "File";
            if ( path.Trim().EndsWith( "\\" ) || path.Trim().EndsWith("/") ||
                 string.IsNullOrEmpty( Path.GetExtension( path ) ) )
            {
                itemType = "Directory";
            }
            return PowerShellInvoke(
                    string.Format("New-Item {0} -ItemType {1} -verbose -Force", path, itemType ),
                    false,
                    testContext ).First().ImmediateBaseObject;
        }
 
        public static void RemoveItem( string path, bool ignoreMissingItem = false, TestContext testContext = null )
        {
            //PowerShellInvoke(ignoreMissingItem, "Remove-Item {0} -verbose -recurse", path);
            PowerShellInvoke(
                    string.Format( "Remove-Item {0} -verbose -recurse", path ),
                    ignoreMissingItem,
                    testContext );
        }
 
        // TODO: Move to base class of TProvider
 
        protected virtual void CopyItem( string path, string destination )
        {
            PowerShellInvoke( "Copy-Item {0} {1};", path, destination );
        }
 
        protected virtual dynamic GetItem( string path )
        {
            return PowerShellInvoke( "Get-Item {0};", path );
        }
 
        protected virtual bool IsItemContainer( string path )
        {
            return
                    (bool)
                            PowerShellInvoke( "Get-Item {0} | %{{ $_.PsIsContainer }}", path )
                                    .Single()
                                    .ImmediateBaseObject;
        }
 
        protected virtual void CopyItemTest( string path, string destination )
        {
            using ( new PSTempItem( path ) )
            {
                using ( new PSTempItem( destination ) )
                {
                    // ReSharper disable PossibleMultipleEnumeration
                    RemoveItem( destination );
                    IEnumerable<string> fileNames = Enumerable.Range( 0, 3 ).Select( count => "Item" + count + ".item" );
 
                    if ( path.EndsWith( "\\" ) )
                    {
                        foreach ( string name in fileNames )
                        {
                            NewItem( Path.Combine( path, name ) );
                        }
                    }
                    CopyItem( path, destination );
                    AttemptAssertion( () => TestPath( destination ) );
                    if ( path.EndsWith( "\\" ) )
                    {
                        Assert.IsTrue( IsItemContainer( destination ) );
                        foreach ( string name in fileNames )
                        {
                            Assert.IsTrue( TestPath( Path.Combine( destination, name ) ) );
                        }
                    }
                    // ReSharper restore PossibleMultipleEnumeration
                }
            }
        }
 
        // for when the first attempt may or may not work due to latency
 
        protected virtual void MoveItem( string path, string destination )
        {
            PowerShellInvoke( "Move-Item {0} {1};", path, destination );
        }
 
        protected void AttemptAssertion( Func<bool> assertion, bool expected = true, int pulses = 6 )
        {
            // if we expect false, check for false
            Func<bool> modifiedAssertion = ( expected ) ? assertion : () => !assertion();
            int count = 0;
 
            while ( !modifiedAssertion() &&
                    count < pulses )
            {
                Thread.Sleep( 400 );
                count++;
            }
 
            if ( expected )
            {
                Assert.IsTrue( assertion() );
            }
            else
            {
                Assert.IsFalse( assertion() );
            }
        }
 
        protected virtual void MoveItemTest( string path, string destination )
        {
            using ( new PSTempItem( path ) )
            {
                using ( new PSTempItem( destination ) )
                {
                    // ReSharper disable PossibleMultipleEnumeration
                    RemoveItem( destination );
                    IEnumerable<string> fileNames = Enumerable.Range( 0, 3 ).Select( count => "Item" + count + ".item" );
                    if ( path.EndsWith( "\\" ) )
                    {
                        foreach ( string name in fileNames )
                        {
                            NewItem( Path.Combine( path, name ) );
                        }
                    }
 
                    MoveItem( path, destination );
                    AttemptAssertion( () => TestPath( path ), false );
                    AttemptAssertion( () => TestPath( destination ) );
                    if ( path.EndsWith( "\\" ) )
                    {
                        Assert.IsTrue( IsItemContainer( destination ) );
                        foreach ( string name in fileNames )
                        {
                            Assert.IsFalse( TestPath( Path.Combine( path, name ) ) );
                            Assert.IsTrue( TestPath( Path.Combine( destination, name ) ) );
                        }
                    }
                    // ReSharper restore PossibleMultipleEnumeration
                }
            }
        }
    }
}