1 #region Copyright (c) 2002-2003, James W. Newkirk, Michael C. Two, Alexei A. Vorontsov, Charlie Poole, Philip A. Craig
2 /************************************************************************************
4 ' Copyright 2002-2003 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov, Charlie Poole
5 ' Copyright 2000-2002 Philip A. Craig
7 ' This software is provided 'as-is', without any express or implied warranty. In no
8 ' event will the authors be held liable for any damages arising from the use of this
11 ' Permission is granted to anyone to use this software for any purpose, including
12 ' commercial applications, and to alter it and redistribute it freely, subject to the
13 ' following restrictions:
15 ' 1. The origin of this software must not be misrepresented; you must not claim that
16 ' you wrote the original software. If you use this software in a product, an
17 ' acknowledgment (see the following) in the product documentation is required.
19 ' Portions Copyright 2002-2003 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov, Charlie Poole
20 ' or Copyright 2000-2002 Philip A. Craig
22 ' 2. Altered source versions must be plainly marked as such, and must not be
23 ' misrepresented as being the original software.
25 ' 3. This notice may not be removed or altered from any source distribution.
27 '***********************************************************************************/
31 using System
.Collections
;
33 using System
.Xml
.Schema
;
35 using System
.Threading
;
41 /// Types of changes that may occur to a config
43 public enum ProjectChangeType
53 /// Arguments for a project event
55 public class ProjectEventArgs
: EventArgs
57 public ProjectChangeType type
;
58 public string configName
;
60 public ProjectEventArgs( ProjectChangeType type
, string configName
)
63 this.configName
= configName
;
68 /// Delegate to be used to handle project events
70 public delegate void ProjectEventHandler( object sender
, ProjectEventArgs e
);
73 /// Class that represents an NUnit test project
75 public class NUnitProject
77 #region Static and instance variables
80 /// Used to generate default names for projects
82 private static int projectSeed
= 0;
85 /// The extension used for test projects
87 private static readonly string nunitExtension
= ".nunit";
90 /// Path to the file storing this project
92 protected string projectPath
;
95 /// Whether the project is dirty
97 protected bool isDirty
= false;
100 /// Collection of configs for the project
102 protected ProjectConfigCollection configs
;
105 /// The currently active configuration
107 private ProjectConfig activeConfig
;
110 /// Flag indicating that this project is a
111 /// temporary wrapper for an assembly.
113 private bool isAssemblyWrapper
= false;
119 public NUnitProject( string projectPath
)
121 this.projectPath
= Path
.GetFullPath( projectPath
);
122 configs
= new ProjectConfigCollection( this );
127 #region Static Methods
129 // True if it's one of our project types
130 public static bool IsProjectFile( string path
)
132 return Path
.GetExtension( path
) == nunitExtension
;
135 // True if it's ours or one we can load
136 public static bool CanLoadAsProject( string path
)
138 return IsProjectFile( path
) ||
139 VSProject
.IsProjectFile( path
) ||
140 VSProject
.IsSolutionFile( path
);
143 public static string GenerateProjectName()
145 return string.Format( "Project{0}", ++projectSeed
);
148 public static NUnitProject
EmptyProject()
150 return new NUnitProject( GenerateProjectName() );
153 public static NUnitProject
NewProject()
155 NUnitProject project
= EmptyProject();
157 project
.Configs
.Add( "Debug" );
158 project
.Configs
.Add( "Release" );
159 project
.IsDirty
= false;
165 /// Return a test project by either loading it from
166 /// the supplied path, creating one from a VS file
167 /// or wrapping an assembly.
169 public static NUnitProject
LoadProject( string path
)
171 if ( NUnitProject
.IsProjectFile( path
) )
173 NUnitProject project
= new NUnitProject( path
);
177 else if ( VSProject
.IsProjectFile( path
) )
178 return NUnitProject
.FromVSProject( path
);
179 else if ( VSProject
.IsSolutionFile( path
) )
180 return NUnitProject
.FromVSSolution( path
);
182 return NUnitProject
.FromAssembly( path
);
187 /// Creates a project to wrap a list of assemblies
189 public static NUnitProject
FromAssemblies( string[] assemblies
)
191 // if only one assembly is passed in then the configuration file
192 // should follow the name of the assembly. This will only happen
193 // if the LoadAssembly method is called. Currently the console ui
194 // does not differentiate between having one or multiple assemblies
196 if ( assemblies
.Length
== 1)
197 return NUnitProject
.FromAssembly(assemblies
[0]);
200 NUnitProject project
= NUnitProject
.EmptyProject();
201 ProjectConfig config
= new ProjectConfig( "Default" );
202 foreach( string assembly
in assemblies
)
204 string fullPath
= Path
.GetFullPath( assembly
);
206 if ( !File
.Exists( fullPath
) )
207 throw new FileNotFoundException( string.Format( "Assembly not found: {0}", fullPath
) );
209 config
.Assemblies
.Add( fullPath
);
212 project
.Configs
.Add( config
);
214 // TODO: Deduce application base, and provide a
215 // better value for loadpath and project path
216 // analagous to how new projects are handled
217 string basePath
= Path
.GetDirectoryName( Path
.GetFullPath( assemblies
[0] ) );
218 project
.projectPath
= Path
.Combine( basePath
, project
.Name
+ ".nunit" );
220 project
.IsDirty
= true;
226 /// Creates a project to wrap an assembly
228 public static NUnitProject
FromAssembly( string assemblyPath
)
230 if ( !File
.Exists( assemblyPath
) )
231 throw new FileNotFoundException( string.Format( "Assembly not found: {0}", assemblyPath
) );
233 string fullPath
= Path
.GetFullPath( assemblyPath
);
235 NUnitProject project
= new NUnitProject( fullPath
);
237 ProjectConfig config
= new ProjectConfig( "Default" );
238 config
.Assemblies
.Add( fullPath
);
239 project
.Configs
.Add( config
);
241 project
.isAssemblyWrapper
= true;
242 project
.IsDirty
= false;
247 public static NUnitProject
FromVSProject( string vsProjectPath
)
249 NUnitProject project
= new NUnitProject( Path
.GetFullPath( vsProjectPath
) );
251 VSProject vsProject
= new VSProject( vsProjectPath
);
252 project
.Add( vsProject
);
254 project
.isDirty
= false;
259 public static NUnitProject
FromVSSolution( string solutionPath
)
261 NUnitProject project
= new NUnitProject( Path
.GetFullPath( solutionPath
) );
263 string solutionDirectory
= Path
.GetDirectoryName( solutionPath
);
264 StreamReader reader
= new StreamReader( solutionPath
);
266 char[] delims
= { '=', ',' }
;
267 char[] trimchars
= { ' ', '"' }
;
269 string line
= reader
.ReadLine();
270 while ( line
!= null )
272 if ( line
.StartsWith( "Project" ) )
274 string[] parts
= line
.Split( delims
);
275 string vsProjectPath
= Path
.Combine( solutionDirectory
, parts
[2].Trim(trimchars
) );
277 if ( VSProject
.IsProjectFile( vsProjectPath
) )
278 project
.Add( new VSProject( vsProjectPath
) );
281 line
= reader
.ReadLine();
284 project
.isDirty
= false;
290 /// Figure out the proper name to be used when saving a file.
292 public static string ProjectPathFromFile( string path
)
294 string fileName
= Path
.GetFileNameWithoutExtension( path
) + nunitExtension
;
295 return Path
.Combine( Path
.GetDirectoryName( path
), fileName
);
300 #region Properties and Events
302 public static int ProjectSeed
304 get { return projectSeed; }
305 set { projectSeed = value; }
309 /// The path to which a project will be saved.
311 public string ProjectPath
313 get { return projectPath; }
316 projectPath
= Path
.GetFullPath( value );
322 /// The base path for the project is the
323 /// directory part of the project path.
325 public string BasePath
327 get { return Path.GetDirectoryName( projectPath ); }
331 /// The name of the project.
335 get { return Path.GetFileNameWithoutExtension( projectPath ); }
338 public ProjectConfig ActiveConfig
342 // In case the previous active config was removed
343 if ( activeConfig
!= null && !configs
.Contains( activeConfig
) )
346 // In case no active config is set or it was removed
347 if ( activeConfig
== null && configs
.Count
> 0 )
348 activeConfig
= configs
[0];
354 // Safe access to name of the active config
355 public string ActiveConfigName
359 ProjectConfig config
= ActiveConfig
;
360 return config
== null ? null : config
.Name
;
364 public bool IsLoadable
368 return ActiveConfig
!= null &&
369 ActiveConfig
.Assemblies
.Count
> 0;
373 // A project made from a single assembly is treated
374 // as a transparent wrapper for some purposes until
375 // a change is made to it.
376 public bool IsAssemblyWrapper
378 get { return isAssemblyWrapper; }
381 public string ConfigurationFile
386 return isAssemblyWrapper
387 ? Path
.GetFileName( projectPath
) + ".config"
388 : Path
.GetFileNameWithoutExtension( projectPath
) + ".config";
394 get { return isDirty; }
395 set { isDirty = value; }
398 public ProjectConfigCollection Configs
400 get { return configs; }
403 public event ProjectEventHandler Changed
;
407 #region Instance Methods
409 public void SetActiveConfig( int index
)
411 activeConfig
= configs
[index
];
412 OnProjectChange( ProjectChangeType
.ActiveConfig
, activeConfig
.Name
);
415 public void SetActiveConfig( string name
)
417 foreach( ProjectConfig config
in configs
)
419 if ( config
.Name
== name
)
421 activeConfig
= config
;
422 OnProjectChange( ProjectChangeType
.ActiveConfig
, activeConfig
.Name
);
428 public void OnProjectChange( ProjectChangeType type
, string configName
)
432 if ( isAssemblyWrapper
)
434 projectPath
= Path
.ChangeExtension( projectPath
, ".nunit" );
435 isAssemblyWrapper
= false;
438 if ( Changed
!= null )
439 Changed( this, new ProjectEventArgs( type
, configName
) );
441 if ( type
== ProjectChangeType
.RemoveConfig
&& activeConfig
.Name
== configName
)
443 if ( configs
.Count
> 0 )
444 SetActiveConfig( 0 );
448 public void Add( VSProject vsProject
)
450 foreach( VSProjectConfig vsConfig
in vsProject
.Configs
)
452 string name
= vsConfig
.Name
;
454 if ( !this.Configs
.Contains( name
) )
455 this.Configs
.Add( name
);
457 ProjectConfig config
= this.Configs
[name
];
459 foreach ( string assembly
in vsConfig
.Assemblies
)
460 config
.Assemblies
.Add( assembly
);
466 XmlTextReader reader
= new XmlTextReader( projectPath
);
468 string activeConfigName
= null;
469 ProjectConfig currentConfig
= null;
473 reader
.MoveToContent();
474 if ( reader
.NodeType
!= XmlNodeType
.Element
|| reader
.Name
!= "NUnitProject" )
475 throw new ProjectFormatException(
476 "Invalid project format: <NUnitProject> expected.",
477 reader
.LineNumber
, reader
.LinePosition
);
479 while( reader
.Read() )
480 if ( reader
.NodeType
== XmlNodeType
.Element
)
481 switch( reader
.Name
)
484 if ( reader
.NodeType
== XmlNodeType
.Element
)
485 activeConfigName
= reader
.GetAttribute( "activeconfig" );
489 if ( reader
.NodeType
== XmlNodeType
.Element
)
491 string configName
= reader
.GetAttribute( "name" );
492 currentConfig
= new ProjectConfig( configName
);
493 currentConfig
.BasePath
= reader
.GetAttribute( "appbase" );
494 currentConfig
.ConfigurationFile
= reader
.GetAttribute( "configfile" );
496 string binpath
= reader
.GetAttribute( "binpath" );
497 string type
= reader
.GetAttribute( "binpathtype" );
499 if ( binpath
== null )
500 currentConfig
.BinPathType
= BinPathType
.Auto
;
502 currentConfig
.BinPathType
= BinPathType
.Manual
;
504 currentConfig
.BinPathType
= (BinPathType
)Enum
.Parse( typeof( BinPathType
), type
, true );
505 Configs
.Add( currentConfig
);
506 if ( configName
== activeConfigName
)
507 activeConfig
= currentConfig
;
509 else if ( reader
.NodeType
== XmlNodeType
.EndElement
)
510 currentConfig
= null;
514 if ( reader
.NodeType
== XmlNodeType
.Element
&& currentConfig
!= null )
516 string path
= reader
.GetAttribute( "path" );
517 string test
= reader
.GetAttribute( "test" );
518 bool hasTests
= test
== null ? true : bool.Parse( test
);
519 currentConfig
.Assemblies
.Add(
520 Path
.Combine( currentConfig
.BasePath
, path
),
529 this.IsDirty
= false;
531 catch( XmlException e
)
533 throw new ProjectFormatException(
534 string.Format( "Invalid project format: {0}", e
.Message
),
535 e
.LineNumber
, e
.LinePosition
);
539 throw new ProjectFormatException(
540 string.Format( "Invalid project format: {0} Line {1}, Position {2}",
541 e
.Message
, reader
.LineNumber
, reader
.LinePosition
),
542 reader
.LineNumber
, reader
.LinePosition
);
552 projectPath
= ProjectPathFromFile( projectPath
);
554 XmlTextWriter writer
= new XmlTextWriter( projectPath
, System
.Text
.Encoding
.UTF8
);
555 writer
.Formatting
= Formatting
.Indented
;
557 writer
.WriteStartElement( "NUnitProject" );
559 if ( configs
.Count
> 0 )
561 writer
.WriteStartElement( "Settings" );
562 writer
.WriteAttributeString( "activeconfig", ActiveConfigName
);
563 writer
.WriteEndElement();
566 foreach( ProjectConfig config
in Configs
)
568 writer
.WriteStartElement( "Config" );
569 writer
.WriteAttributeString( "name", config
.Name
);
570 if ( config
.RelativeBasePath
!= null )
571 writer
.WriteAttributeString( "appbase", config
.RelativeBasePath
);
573 string configFile
= config
.ConfigurationFile
;
574 if ( configFile
!= null && configFile
!= this.ConfigurationFile
)
575 writer
.WriteAttributeString( "configfile", config
.ConfigurationFile
);
577 if ( config
.BinPathType
== BinPathType
.Manual
)
578 writer
.WriteAttributeString( "binpath", config
.PrivateBinPath
);
580 writer
.WriteAttributeString( "binpathtype", config
.BinPathType
.ToString() );
582 foreach( AssemblyListItem assembly
in config
.Assemblies
)
584 writer
.WriteStartElement( "assembly" );
585 writer
.WriteAttributeString( "path", config
.RelativePathTo( assembly
.FullPath
) );
586 if ( !assembly
.HasTests
)
587 writer
.WriteAttributeString( "test", "false" );
588 writer
.WriteEndElement();
591 writer
.WriteEndElement();
594 writer
.WriteEndElement();
597 this.IsDirty
= false;
599 // Once we save a project, it's no longer
600 // loaded as an assembly wrapper on reload.
601 this.isAssemblyWrapper
= false;
604 public void Save( string projectPath
)
606 this.ProjectPath
= projectPath
;