a575963da9
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
657 lines
17 KiB
C#
657 lines
17 KiB
C#
// ****************************************************************
|
|
// This is free software licensed under the NUnit license. You
|
|
// may obtain a copy of the license as well as information regarding
|
|
// copyright ownership at http://nunit.org/?p=license&r=2.4.
|
|
// ****************************************************************
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Xml;
|
|
using System.Xml.Schema;
|
|
using System.IO;
|
|
using System.Threading;
|
|
using NUnit.Core;
|
|
|
|
namespace NUnit.Util
|
|
{
|
|
/// <summary>
|
|
/// Types of changes that may occur to a config
|
|
/// </summary>
|
|
public enum ProjectChangeType
|
|
{
|
|
ActiveConfig,
|
|
AddConfig,
|
|
RemoveConfig,
|
|
UpdateConfig,
|
|
Other
|
|
}
|
|
|
|
/// <summary>
|
|
/// Arguments for a project event
|
|
/// </summary>
|
|
public class ProjectEventArgs : EventArgs
|
|
{
|
|
public ProjectChangeType type;
|
|
public string configName;
|
|
|
|
public ProjectEventArgs( ProjectChangeType type, string configName )
|
|
{
|
|
this.type = type;
|
|
this.configName = configName;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delegate to be used to handle project events
|
|
/// </summary>
|
|
public delegate void ProjectEventHandler( object sender, ProjectEventArgs e );
|
|
|
|
/// <summary>
|
|
/// Class that represents an NUnit test project
|
|
/// </summary>
|
|
public class NUnitProject
|
|
{
|
|
#region Static and instance variables
|
|
|
|
/// <summary>
|
|
/// Used to generate default names for projects
|
|
/// </summary>
|
|
private static int projectSeed = 0;
|
|
|
|
/// <summary>
|
|
/// The extension used for test projects
|
|
/// </summary>
|
|
private static readonly string nunitExtension = ".nunit";
|
|
|
|
/// <summary>
|
|
/// Path to the file storing this project
|
|
/// </summary>
|
|
private string projectPath;
|
|
|
|
/// <summary>
|
|
/// Application Base for the project. Since this
|
|
/// can be null, always fetch from the property
|
|
/// rather than using the field directly.
|
|
/// </summary>
|
|
private string basePath;
|
|
|
|
/// <summary>
|
|
/// Whether the project is dirty
|
|
/// </summary>
|
|
private bool isDirty = false;
|
|
|
|
/// <summary>
|
|
/// Collection of configs for the project
|
|
/// </summary>
|
|
private ProjectConfigCollection configs;
|
|
|
|
/// <summary>
|
|
/// The currently active configuration
|
|
/// </summary>
|
|
private ProjectConfig activeConfig;
|
|
|
|
/// <summary>
|
|
/// Flag indicating that this project is a
|
|
/// temporary wrapper for an assembly.
|
|
/// </summary>
|
|
private bool isAssemblyWrapper = false;
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
public NUnitProject( string projectPath )
|
|
{
|
|
this.projectPath = Path.GetFullPath( projectPath );
|
|
configs = new ProjectConfigCollection( this );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Static Methods
|
|
// True if it's one of our project types
|
|
public static bool IsProjectFile( string path )
|
|
{
|
|
return Path.GetExtension( path ) == nunitExtension;
|
|
}
|
|
|
|
// True if it's ours or one we can load
|
|
public static bool CanLoadAsProject( string path )
|
|
{
|
|
return IsProjectFile( path ) ||
|
|
VSProject.IsProjectFile( path ) ||
|
|
VSProject.IsSolutionFile( path );
|
|
}
|
|
|
|
public static string GenerateProjectName()
|
|
{
|
|
return string.Format( "Project{0}", ++projectSeed );
|
|
}
|
|
|
|
public static NUnitProject EmptyProject()
|
|
{
|
|
return new NUnitProject( GenerateProjectName() );
|
|
}
|
|
|
|
public static NUnitProject NewProject()
|
|
{
|
|
NUnitProject project = EmptyProject();
|
|
|
|
project.Configs.Add( "Debug" );
|
|
project.Configs.Add( "Release" );
|
|
project.IsDirty = false;
|
|
|
|
return project;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return a test project by either loading it from
|
|
/// the supplied path, creating one from a VS file
|
|
/// or wrapping an assembly.
|
|
/// </summary>
|
|
public static NUnitProject LoadProject( string path )
|
|
{
|
|
if ( NUnitProject.IsProjectFile( path ) )
|
|
{
|
|
NUnitProject project = new NUnitProject( path );
|
|
project.Load();
|
|
return project;
|
|
}
|
|
else if ( VSProject.IsProjectFile( path ) )
|
|
return NUnitProject.FromVSProject( path );
|
|
else if ( VSProject.IsSolutionFile( path ) )
|
|
return NUnitProject.FromVSSolution( path );
|
|
else
|
|
return NUnitProject.FromAssembly( path );
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a project to wrap a list of assemblies
|
|
/// </summary>
|
|
public static NUnitProject FromAssemblies( string[] assemblies )
|
|
{
|
|
// if only one assembly is passed in then the configuration file
|
|
// should follow the name of the assembly. This will only happen
|
|
// if the LoadAssembly method is called. Currently the console ui
|
|
// does not differentiate between having one or multiple assemblies
|
|
// passed in.
|
|
if ( assemblies.Length == 1)
|
|
return NUnitProject.FromAssembly(assemblies[0]);
|
|
|
|
|
|
NUnitProject project = NUnitProject.EmptyProject();
|
|
ProjectConfig config = new ProjectConfig( "Default" );
|
|
foreach( string assembly in assemblies )
|
|
{
|
|
string fullPath = Path.GetFullPath( assembly );
|
|
|
|
if ( !File.Exists( fullPath ) )
|
|
throw new FileNotFoundException( string.Format( "Assembly not found: {0}", fullPath ) );
|
|
|
|
config.Assemblies.Add( fullPath );
|
|
}
|
|
|
|
project.Configs.Add( config );
|
|
|
|
// TODO: Deduce application base, and provide a
|
|
// better value for loadpath and project path
|
|
// analagous to how new projects are handled
|
|
string basePath = Path.GetDirectoryName( Path.GetFullPath( assemblies[0] ) );
|
|
project.projectPath = Path.Combine( basePath, project.Name + ".nunit" );
|
|
|
|
project.IsDirty = true;
|
|
|
|
return project;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a project to wrap an assembly
|
|
/// </summary>
|
|
public static NUnitProject FromAssembly( string assemblyPath )
|
|
{
|
|
if ( !File.Exists( assemblyPath ) )
|
|
throw new FileNotFoundException( string.Format( "Assembly not found: {0}", assemblyPath ) );
|
|
|
|
string fullPath = Path.GetFullPath( assemblyPath );
|
|
|
|
NUnitProject project = new NUnitProject( fullPath );
|
|
|
|
ProjectConfig config = new ProjectConfig( "Default" );
|
|
config.Assemblies.Add( fullPath );
|
|
project.Configs.Add( config );
|
|
|
|
project.isAssemblyWrapper = true;
|
|
project.IsDirty = false;
|
|
|
|
return project;
|
|
}
|
|
|
|
public static NUnitProject FromVSProject( string vsProjectPath )
|
|
{
|
|
NUnitProject project = new NUnitProject( Path.GetFullPath( vsProjectPath ) );
|
|
|
|
VSProject vsProject = new VSProject( vsProjectPath );
|
|
project.Add( vsProject );
|
|
|
|
project.isDirty = false;
|
|
|
|
return project;
|
|
}
|
|
|
|
public static NUnitProject FromVSSolution( string solutionPath )
|
|
{
|
|
NUnitProject project = new NUnitProject( Path.GetFullPath( solutionPath ) );
|
|
|
|
string solutionDirectory = Path.GetDirectoryName( solutionPath );
|
|
using(StreamReader reader = new StreamReader( solutionPath ))
|
|
{
|
|
char[] delims = { '=', ',' };
|
|
char[] trimchars = { ' ', '"' };
|
|
|
|
string line = reader.ReadLine();
|
|
while ( line != null )
|
|
{
|
|
if ( line.StartsWith( "Project" ) )
|
|
{
|
|
string[] parts = line.Split( delims );
|
|
string vsProjectPath = parts[2].Trim(trimchars);
|
|
|
|
if ( VSProject.IsProjectFile( vsProjectPath ) )
|
|
project.Add( new VSProject( Path.Combine( solutionDirectory, vsProjectPath ) ) );
|
|
}
|
|
|
|
line = reader.ReadLine();
|
|
}
|
|
}
|
|
|
|
project.isDirty = false;
|
|
|
|
return project;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Figure out the proper name to be used when saving a file.
|
|
/// </summary>
|
|
public static string ProjectPathFromFile( string path )
|
|
{
|
|
string fileName = Path.GetFileNameWithoutExtension( path ) + nunitExtension;
|
|
return Path.Combine( Path.GetDirectoryName( path ), fileName );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties and Events
|
|
|
|
public static int ProjectSeed
|
|
{
|
|
get { return projectSeed; }
|
|
set { projectSeed = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The path to which a project will be saved.
|
|
/// </summary>
|
|
public string ProjectPath
|
|
{
|
|
get { return projectPath; }
|
|
set
|
|
{
|
|
projectPath = Path.GetFullPath( value );
|
|
isDirty = true;
|
|
}
|
|
}
|
|
|
|
public string DefaultBasePath
|
|
{
|
|
get { return Path.GetDirectoryName( projectPath ); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates whether a base path was specified for the project
|
|
/// </summary>
|
|
public bool BasePathSpecified
|
|
{
|
|
get
|
|
{
|
|
return basePath != null && basePath != string.Empty;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The base path for the project. Constructor sets
|
|
/// it to the directory part of the project path.
|
|
/// </summary>
|
|
public string BasePath
|
|
{
|
|
get
|
|
{
|
|
if ( !BasePathSpecified )
|
|
return DefaultBasePath;
|
|
return basePath;
|
|
}
|
|
set
|
|
{
|
|
basePath = value;
|
|
|
|
if ( basePath != null && basePath != string.Empty
|
|
&& !Path.IsPathRooted( basePath ) )
|
|
{
|
|
basePath = Path.Combine(
|
|
DefaultBasePath,
|
|
basePath );
|
|
}
|
|
|
|
basePath = PathUtils.Canonicalize( basePath );
|
|
OnProjectChange( ProjectChangeType.Other, null );
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The name of the project.
|
|
/// </summary>
|
|
public string Name
|
|
{
|
|
get { return Path.GetFileNameWithoutExtension( projectPath ); }
|
|
}
|
|
|
|
public ProjectConfig ActiveConfig
|
|
{
|
|
get
|
|
{
|
|
// In case the previous active config was removed
|
|
if ( activeConfig != null && !configs.Contains( activeConfig ) )
|
|
activeConfig = null;
|
|
|
|
// In case no active config is set or it was removed
|
|
if ( activeConfig == null && configs.Count > 0 )
|
|
activeConfig = configs[0];
|
|
|
|
return activeConfig;
|
|
}
|
|
}
|
|
|
|
// Safe access to name of the active config
|
|
public string ActiveConfigName
|
|
{
|
|
get
|
|
{
|
|
ProjectConfig config = ActiveConfig;
|
|
return config == null ? null : config.Name;
|
|
}
|
|
}
|
|
|
|
public bool IsLoadable
|
|
{
|
|
get
|
|
{
|
|
return ActiveConfig != null &&
|
|
ActiveConfig.Assemblies.Count > 0;
|
|
}
|
|
}
|
|
|
|
// A project made from a single assembly is treated
|
|
// as a transparent wrapper for some purposes until
|
|
// a change is made to it.
|
|
public bool IsAssemblyWrapper
|
|
{
|
|
get { return isAssemblyWrapper; }
|
|
}
|
|
|
|
public string ConfigurationFile
|
|
{
|
|
get
|
|
{
|
|
// TODO: Check this
|
|
return isAssemblyWrapper
|
|
? Path.GetFileName( projectPath ) + ".config"
|
|
: Path.GetFileNameWithoutExtension( projectPath ) + ".config";
|
|
}
|
|
}
|
|
|
|
public bool IsDirty
|
|
{
|
|
get { return isDirty; }
|
|
set { isDirty = value; }
|
|
}
|
|
|
|
public ProjectConfigCollection Configs
|
|
{
|
|
get { return configs; }
|
|
}
|
|
|
|
public event ProjectEventHandler Changed;
|
|
|
|
#endregion
|
|
|
|
#region Instance Methods
|
|
|
|
public void SetActiveConfig( int index )
|
|
{
|
|
activeConfig = configs[index];
|
|
OnProjectChange( ProjectChangeType.ActiveConfig, activeConfig.Name );
|
|
}
|
|
|
|
public void SetActiveConfig( string name )
|
|
{
|
|
foreach( ProjectConfig config in configs )
|
|
{
|
|
if ( config.Name == name )
|
|
{
|
|
activeConfig = config;
|
|
OnProjectChange( ProjectChangeType.ActiveConfig, activeConfig.Name );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// public int IndexOf( string name )
|
|
// {
|
|
// for( int index = 0; index < configs.Count; index++ )
|
|
// if( configs[index].Name == name )
|
|
// return index;
|
|
//
|
|
// return -1;
|
|
// }
|
|
|
|
public void OnProjectChange( ProjectChangeType type, string configName )
|
|
{
|
|
isDirty = true;
|
|
|
|
if ( isAssemblyWrapper )
|
|
{
|
|
projectPath = Path.ChangeExtension( projectPath, ".nunit" );
|
|
isAssemblyWrapper = false;
|
|
}
|
|
|
|
if ( Changed != null )
|
|
Changed( this, new ProjectEventArgs( type, configName ) );
|
|
|
|
if ( type == ProjectChangeType.RemoveConfig )
|
|
{
|
|
if ( activeConfig == null || activeConfig.Name == configName )
|
|
{
|
|
if ( configs.Count > 0 )
|
|
SetActiveConfig( 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Add( VSProject vsProject )
|
|
{
|
|
foreach( VSProjectConfig vsConfig in vsProject.Configs )
|
|
{
|
|
string name = vsConfig.Name;
|
|
|
|
if ( !configs.Contains( name ) )
|
|
configs.Add( name );
|
|
|
|
ProjectConfig config = this.Configs[name];
|
|
|
|
foreach ( string assembly in vsConfig.Assemblies )
|
|
config.Assemblies.Add( assembly );
|
|
}
|
|
}
|
|
|
|
public void Load()
|
|
{
|
|
XmlTextReader reader = new XmlTextReader( projectPath );
|
|
|
|
string activeConfigName = null;
|
|
ProjectConfig currentConfig = null;
|
|
|
|
try
|
|
{
|
|
reader.MoveToContent();
|
|
if ( reader.NodeType != XmlNodeType.Element || reader.Name != "NUnitProject" )
|
|
throw new ProjectFormatException(
|
|
"Invalid project format: <NUnitProject> expected.",
|
|
reader.LineNumber, reader.LinePosition );
|
|
|
|
while( reader.Read() )
|
|
if ( reader.NodeType == XmlNodeType.Element )
|
|
switch( reader.Name )
|
|
{
|
|
case "Settings":
|
|
if ( reader.NodeType == XmlNodeType.Element )
|
|
{
|
|
activeConfigName = reader.GetAttribute( "activeconfig" );
|
|
if (activeConfigName == "NUnitAutoConfig")
|
|
activeConfigName = NUnitFramework.BuildConfiguration;
|
|
string appbase = reader.GetAttribute( "appbase" );
|
|
if ( appbase != null )
|
|
this.BasePath = appbase;
|
|
}
|
|
break;
|
|
|
|
case "Config":
|
|
if ( reader.NodeType == XmlNodeType.Element )
|
|
{
|
|
string configName = reader.GetAttribute( "name" );
|
|
currentConfig = new ProjectConfig( configName );
|
|
currentConfig.BasePath = reader.GetAttribute( "appbase" );
|
|
currentConfig.ConfigurationFile = reader.GetAttribute( "configfile" );
|
|
|
|
string binpath = reader.GetAttribute( "binpath" );
|
|
currentConfig.PrivateBinPath = binpath;
|
|
string type = reader.GetAttribute( "binpathtype" );
|
|
if ( type == null )
|
|
if ( binpath == null )
|
|
currentConfig.BinPathType = BinPathType.Auto;
|
|
else
|
|
currentConfig.BinPathType = BinPathType.Manual;
|
|
else
|
|
currentConfig.BinPathType = (BinPathType)Enum.Parse( typeof( BinPathType ), type, true );
|
|
Configs.Add( currentConfig );
|
|
if ( configName == activeConfigName )
|
|
activeConfig = currentConfig;
|
|
}
|
|
else if ( reader.NodeType == XmlNodeType.EndElement )
|
|
currentConfig = null;
|
|
break;
|
|
|
|
case "assembly":
|
|
if ( reader.NodeType == XmlNodeType.Element && currentConfig != null )
|
|
{
|
|
string path = reader.GetAttribute( "path" );
|
|
currentConfig.Assemblies.Add(
|
|
Path.Combine( currentConfig.BasePath, path ) );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
this.IsDirty = false;
|
|
}
|
|
catch( FileNotFoundException )
|
|
{
|
|
throw;
|
|
}
|
|
catch( XmlException e )
|
|
{
|
|
throw new ProjectFormatException(
|
|
string.Format( "Invalid project format: {0}", e.Message ),
|
|
e.LineNumber, e.LinePosition );
|
|
}
|
|
catch( Exception e )
|
|
{
|
|
throw new ProjectFormatException(
|
|
string.Format( "Invalid project format: {0} Line {1}, Position {2}",
|
|
e.Message, reader.LineNumber, reader.LinePosition ),
|
|
reader.LineNumber, reader.LinePosition );
|
|
}
|
|
finally
|
|
{
|
|
reader.Close();
|
|
}
|
|
}
|
|
|
|
public void Save()
|
|
{
|
|
projectPath = ProjectPathFromFile( projectPath );
|
|
|
|
XmlTextWriter writer = new XmlTextWriter( projectPath, System.Text.Encoding.UTF8 );
|
|
writer.Formatting = Formatting.Indented;
|
|
|
|
writer.WriteStartElement( "NUnitProject" );
|
|
|
|
if ( configs.Count > 0 || this.BasePath != this.DefaultBasePath )
|
|
{
|
|
writer.WriteStartElement( "Settings" );
|
|
if ( configs.Count > 0 )
|
|
writer.WriteAttributeString( "activeconfig", ActiveConfigName );
|
|
if ( this.BasePath != this.DefaultBasePath )
|
|
writer.WriteAttributeString( "appbase", this.BasePath );
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
foreach( ProjectConfig config in Configs )
|
|
{
|
|
writer.WriteStartElement( "Config" );
|
|
writer.WriteAttributeString( "name", config.Name );
|
|
string appbase = config.BasePath;
|
|
if ( !PathUtils.SamePathOrUnder( this.BasePath, appbase ) )
|
|
writer.WriteAttributeString( "appbase", appbase );
|
|
else if ( config.RelativeBasePath != null )
|
|
writer.WriteAttributeString( "appbase", config.RelativeBasePath );
|
|
|
|
string configFile = config.ConfigurationFile;
|
|
if ( configFile != null && configFile != this.ConfigurationFile )
|
|
writer.WriteAttributeString( "configfile", config.ConfigurationFile );
|
|
|
|
if ( config.BinPathType == BinPathType.Manual )
|
|
writer.WriteAttributeString( "binpath", config.PrivateBinPath );
|
|
else
|
|
writer.WriteAttributeString( "binpathtype", config.BinPathType.ToString() );
|
|
|
|
foreach( string assembly in config.Assemblies )
|
|
{
|
|
writer.WriteStartElement( "assembly" );
|
|
writer.WriteAttributeString( "path", PathUtils.RelativePath( config.BasePath, assembly ) );
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
|
|
writer.Close();
|
|
this.IsDirty = false;
|
|
|
|
// Once we save a project, it's no longer
|
|
// loaded as an assembly wrapper on reload.
|
|
this.isAssemblyWrapper = false;
|
|
}
|
|
|
|
public void Save( string projectPath )
|
|
{
|
|
this.ProjectPath = projectPath;
|
|
Save();
|
|
}
|
|
#endregion
|
|
}
|
|
}
|