(DISTFILES): Comment out a few missing files.
[mono-project.git] / mcs / nunit20 / util / NUnitProject.cs
blob5f2edd5faaf43ba0a567d51936b42b472af48127
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
9 ' software.
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 '***********************************************************************************/
28 #endregion
30 using System;
31 using System.Collections;
32 using System.Xml;
33 using System.Xml.Schema;
34 using System.IO;
35 using System.Threading;
36 using NUnit.Core;
38 namespace NUnit.Util
40 /// <summary>
41 /// Types of changes that may occur to a config
42 /// </summary>
43 public enum ProjectChangeType
45 ActiveConfig,
46 AddConfig,
47 RemoveConfig,
48 UpdateConfig,
49 Other
52 /// <summary>
53 /// Arguments for a project event
54 /// </summary>
55 public class ProjectEventArgs : EventArgs
57 public ProjectChangeType type;
58 public string configName;
60 public ProjectEventArgs( ProjectChangeType type, string configName )
62 this.type = type;
63 this.configName = configName;
67 /// <summary>
68 /// Delegate to be used to handle project events
69 /// </summary>
70 public delegate void ProjectEventHandler( object sender, ProjectEventArgs e );
72 /// <summary>
73 /// Class that represents an NUnit test project
74 /// </summary>
75 public class NUnitProject
77 #region Static and instance variables
79 /// <summary>
80 /// Used to generate default names for projects
81 /// </summary>
82 private static int projectSeed = 0;
84 /// <summary>
85 /// The extension used for test projects
86 /// </summary>
87 private static readonly string nunitExtension = ".nunit";
89 /// <summary>
90 /// Path to the file storing this project
91 /// </summary>
92 protected string projectPath;
94 /// <summary>
95 /// Whether the project is dirty
96 /// </summary>
97 protected bool isDirty = false;
99 /// <summary>
100 /// Collection of configs for the project
101 /// </summary>
102 protected ProjectConfigCollection configs;
104 /// <summary>
105 /// The currently active configuration
106 /// </summary>
107 private ProjectConfig activeConfig;
109 /// <summary>
110 /// Flag indicating that this project is a
111 /// temporary wrapper for an assembly.
112 /// </summary>
113 private bool isAssemblyWrapper = false;
115 #endregion
117 #region Constructor
119 public NUnitProject( string projectPath )
121 this.projectPath = Path.GetFullPath( projectPath );
122 configs = new ProjectConfigCollection( this );
125 #endregion
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;
161 return project;
164 /// <summary>
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.
168 /// </summary>
169 public static NUnitProject LoadProject( string path )
171 if ( NUnitProject.IsProjectFile( path ) )
173 NUnitProject project = new NUnitProject( path );
174 project.Load();
175 return project;
177 else if ( VSProject.IsProjectFile( path ) )
178 return NUnitProject.FromVSProject( path );
179 else if ( VSProject.IsSolutionFile( path ) )
180 return NUnitProject.FromVSSolution( path );
181 else
182 return NUnitProject.FromAssembly( path );
186 /// <summary>
187 /// Creates a project to wrap a list of assemblies
188 /// </summary>
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
195 // passed in.
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;
222 return project;
225 /// <summary>
226 /// Creates a project to wrap an assembly
227 /// </summary>
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;
244 return project;
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;
256 return project;
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;
286 return project;
289 /// <summary>
290 /// Figure out the proper name to be used when saving a file.
291 /// </summary>
292 public static string ProjectPathFromFile( string path )
294 string fileName = Path.GetFileNameWithoutExtension( path ) + nunitExtension;
295 return Path.Combine( Path.GetDirectoryName( path ), fileName );
298 #endregion
300 #region Properties and Events
302 public static int ProjectSeed
304 get { return projectSeed; }
305 set { projectSeed = value; }
308 /// <summary>
309 /// The path to which a project will be saved.
310 /// </summary>
311 public string ProjectPath
313 get { return projectPath; }
314 set
316 projectPath = Path.GetFullPath( value );
317 isDirty = true;
321 /// <summary>
322 /// The base path for the project is the
323 /// directory part of the project path.
324 /// </summary>
325 public string BasePath
327 get { return Path.GetDirectoryName( projectPath ); }
330 /// <summary>
331 /// The name of the project.
332 /// </summary>
333 public string Name
335 get { return Path.GetFileNameWithoutExtension( projectPath ); }
338 public ProjectConfig ActiveConfig
340 get
342 // In case the previous active config was removed
343 if ( activeConfig != null && !configs.Contains( activeConfig ) )
344 activeConfig = null;
346 // In case no active config is set or it was removed
347 if ( activeConfig == null && configs.Count > 0 )
348 activeConfig = configs[0];
350 return activeConfig;
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
383 get
385 // TODO: Check this
386 return isAssemblyWrapper
387 ? Path.GetFileName( projectPath ) + ".config"
388 : Path.GetFileNameWithoutExtension( projectPath ) + ".config";
392 public bool IsDirty
394 get { return isDirty; }
395 set { isDirty = value; }
398 public ProjectConfigCollection Configs
400 get { return configs; }
403 public event ProjectEventHandler Changed;
405 #endregion
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 );
423 break;
428 public void OnProjectChange( ProjectChangeType type, string configName )
430 isDirty = true;
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 );
464 public void Load()
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 )
483 case "Settings":
484 if ( reader.NodeType == XmlNodeType.Element )
485 activeConfigName = reader.GetAttribute( "activeconfig" );
486 break;
488 case "Config":
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" );
498 if ( type == null )
499 if ( binpath == null )
500 currentConfig.BinPathType = BinPathType.Auto;
501 else
502 currentConfig.BinPathType = BinPathType.Manual;
503 else
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;
511 break;
513 case "assembly":
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 ),
521 hasTests );
523 break;
525 default:
526 break;
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 );
537 catch( Exception e )
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 );
544 finally
546 reader.Close();
550 public void Save()
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 );
579 else
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();
596 writer.Close();
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;
607 Save();
610 #endregion