/src/NUnit/util/NUnitProject.cs
C# | 509 lines | 368 code | 77 blank | 64 comment | 82 complexity | 863c79c7b0baac9225688a606294e8cc MD5 | raw file
1// **************************************************************** 2// This is free software licensed under the NUnit license. You 3// may obtain a copy of the license as well as information regarding 4// copyright ownership at http://nunit.org. 5// **************************************************************** 6 7using System; 8using System.Collections; 9using System.Xml; 10using System.Xml.Schema; 11using System.IO; 12using System.Threading; 13using NUnit.Core; 14 15namespace NUnit.Util 16{ 17 /// <summary> 18 /// Class that represents an NUnit test project 19 /// </summary> 20 public class NUnitProject 21 { 22 #region Constants 23 public static readonly string Extension = ".nunit"; 24 #endregion 25 26 #region Instance variables 27 28 /// <summary> 29 /// Path to the file storing this project 30 /// </summary> 31 private string projectPath; 32 33 /// <summary> 34 /// Application Base for the project. Since this 35 /// can be null, always fetch from the property 36 /// rather than using the field directly. 37 /// </summary> 38 private string basePath; 39 40 /// <summary> 41 /// Whether the project is dirty 42 /// </summary> 43 private bool isDirty = false; 44 45 /// <summary> 46 /// Whether canges have been made requiring a reload 47 /// </summary> 48 private bool reloadRequired = false; 49 50 /// <summary> 51 /// Collection of configs for the project 52 /// </summary> 53 private ProjectConfigCollection configs; 54 55 /// <summary> 56 /// True for NUnit-related projects that follow the config 57 /// of the NUnit build under which they are running. 58 /// </summary> 59 private bool autoConfig; 60 61 /// <summary> 62 /// The currently active configuration 63 /// </summary> 64 private ProjectConfig activeConfig; 65 66 /// <summary> 67 /// Flag indicating that this project is a 68 /// temporary wrapper for an assembly. 69 /// </summary> 70 private bool isAssemblyWrapper = false; 71 72 /// <summary> 73 /// The ProcessModel to be used in loading this project 74 /// </summary> 75 private ProcessModel processModel; 76 77 /// <summary> 78 /// The DomainUsage setting to be used in loading this project 79 /// </summary> 80 private DomainUsage domainUsage; 81 82 #endregion 83 84 #region Constructor 85 86 public NUnitProject( string projectPath ) 87 { 88 this.projectPath = Path.GetFullPath( projectPath ); 89 configs = new ProjectConfigCollection( this ); 90 } 91 92 #endregion 93 94 #region Properties and Events 95 96 /// <summary> 97 /// The path to which a project will be saved. 98 /// </summary> 99 public string ProjectPath 100 { 101 get { return projectPath; } 102 set 103 { 104 projectPath = Path.GetFullPath( value ); 105 isDirty = true; 106 } 107 } 108 109 public string DefaultBasePath 110 { 111 get { return Path.GetDirectoryName( projectPath ); } 112 } 113 114 /// <summary> 115 /// Indicates whether a base path was specified for the project 116 /// </summary> 117 public bool BasePathSpecified 118 { 119 get 120 { 121 return basePath != null && basePath != string.Empty; 122 } 123 } 124 125 /// <summary> 126 /// The base path for the project. Constructor sets 127 /// it to the directory part of the project path. 128 /// </summary> 129 public string BasePath 130 { 131 get 132 { 133 if ( !BasePathSpecified ) 134 return DefaultBasePath; 135 return basePath; 136 } 137 set 138 { 139 basePath = value; 140 141 if (basePath != null && basePath != string.Empty 142 && !Path.IsPathRooted(basePath)) 143 { 144 basePath = Path.Combine( 145 DefaultBasePath, 146 basePath); 147 } 148 149 basePath = PathUtils.Canonicalize(basePath); 150 HasChangesRequiringReload = IsDirty = true; 151 } 152 } 153 154 /// <summary> 155 /// The name of the project. 156 /// </summary> 157 public string Name 158 { 159 get { return Path.GetFileNameWithoutExtension( projectPath ); } 160 } 161 162 public bool AutoConfig 163 { 164 get { return autoConfig; } 165 set { autoConfig = value; } 166 } 167 168 public ProjectConfig ActiveConfig 169 { 170 get 171 { 172 // In case the previous active config was removed 173 if ( activeConfig != null && !configs.Contains( activeConfig ) ) 174 activeConfig = null; 175 176 // In case no active config is set or it was removed 177 if (activeConfig == null && configs.Count > 0) 178 activeConfig = configs[0]; 179 180 return activeConfig; 181 } 182 } 183 184 // Safe access to name of the active config 185 public string ActiveConfigName 186 { 187 get 188 { 189 ProjectConfig config = ActiveConfig; 190 return config == null ? null : config.Name; 191 } 192 } 193 194 public bool IsLoadable 195 { 196 get 197 { 198 return ActiveConfig != null && 199 ActiveConfig.Assemblies.Count > 0; 200 } 201 } 202 203 // A project made from a single assembly is treated 204 // as a transparent wrapper for some purposes until 205 // a change is made to it. 206 public bool IsAssemblyWrapper 207 { 208 get { return isAssemblyWrapper; } 209 set { isAssemblyWrapper = value; } 210 } 211 212 public string ConfigurationFile 213 { 214 get 215 { 216 // TODO: Check this 217 return isAssemblyWrapper 218 ? Path.GetFileName( projectPath ) + ".config" 219 : Path.GetFileNameWithoutExtension( projectPath ) + ".config"; 220 } 221 } 222 223 public bool IsDirty 224 { 225 get { return isDirty; } 226 set 227 { 228 isDirty = value; 229 230 if (isAssemblyWrapper && value == true) 231 { 232 projectPath = Path.ChangeExtension(projectPath, ".nunit"); 233 isAssemblyWrapper = false; 234 HasChangesRequiringReload = true; 235 } 236 } 237 } 238 239 public bool HasChangesRequiringReload 240 { 241 get { return reloadRequired; } 242 set { reloadRequired = value; } 243 } 244 245 public ProcessModel ProcessModel 246 { 247 get { return processModel; } 248 set 249 { 250 processModel = value; 251 HasChangesRequiringReload = IsDirty = true; 252 } 253 } 254 255 public DomainUsage DomainUsage 256 { 257 get { return domainUsage; } 258 set 259 { 260 domainUsage = value; 261 HasChangesRequiringReload = IsDirty = true; 262 } 263 } 264 265 public ProjectConfigCollection Configs 266 { 267 get { return configs; } 268 } 269 #endregion 270 271 #region Static Methods 272 public static bool IsNUnitProjectFile(string path) 273 { 274 return Path.GetExtension(path) == Extension; 275 } 276 277 public static string ProjectPathFromFile(string path) 278 { 279 string fileName = Path.GetFileNameWithoutExtension(path) + NUnitProject.Extension; 280 return Path.Combine(Path.GetDirectoryName(path), fileName); 281 } 282 #endregion 283 284 #region Instance Methods 285 286 public void SetActiveConfig( int index ) 287 { 288 activeConfig = configs[index]; 289 HasChangesRequiringReload = IsDirty = true; 290 } 291 292 public void SetActiveConfig( string name ) 293 { 294 foreach( ProjectConfig config in configs ) 295 { 296 if ( config.Name == name ) 297 { 298 activeConfig = config; 299 HasChangesRequiringReload = IsDirty = true; 300 break; 301 } 302 } 303 } 304 305 public void Add( VSProject vsProject ) 306 { 307 foreach( VSProjectConfig vsConfig in vsProject.Configs ) 308 { 309 string name = vsConfig.Name; 310 311 if ( !configs.Contains( name ) ) 312 configs.Add( name ); 313 314 ProjectConfig config = this.Configs[name]; 315 316 foreach ( string assembly in vsConfig.Assemblies ) 317 config.Assemblies.Add( assembly ); 318 } 319 } 320 321 public void Load() 322 { 323 XmlTextReader reader = new XmlTextReader( projectPath ); 324 325 string activeConfigName = null; 326 ProjectConfig currentConfig = null; 327 328 try 329 { 330 reader.MoveToContent(); 331 if ( reader.NodeType != XmlNodeType.Element || reader.Name != "NUnitProject" ) 332 throw new ProjectFormatException( 333 "Invalid project format: <NUnitProject> expected.", 334 reader.LineNumber, reader.LinePosition ); 335 336 while( reader.Read() ) 337 if ( reader.NodeType == XmlNodeType.Element ) 338 switch( reader.Name ) 339 { 340 case "Settings": 341 if ( reader.NodeType == XmlNodeType.Element ) 342 { 343 activeConfigName = reader.GetAttribute( "activeconfig" ); 344 345 string autoConfig = reader.GetAttribute("autoconfig"); 346 if (autoConfig != null) 347 this.AutoConfig = autoConfig.ToLower() == "true"; 348 if (this.AutoConfig) 349 activeConfigName = NUnitConfiguration.BuildConfiguration; 350 351 string appbase = reader.GetAttribute( "appbase" ); 352 if ( appbase != null ) 353 this.BasePath = appbase; 354 355 string processModel = reader.GetAttribute("processModel"); 356 if (processModel != null) 357 this.ProcessModel = (ProcessModel)Enum.Parse(typeof(ProcessModel), processModel); 358 359 string domainUsage = reader.GetAttribute("domainUsage"); 360 if (domainUsage != null) 361 this.DomainUsage = (DomainUsage)Enum.Parse(typeof(DomainUsage), domainUsage); 362 } 363 break; 364 365 case "Config": 366 if ( reader.NodeType == XmlNodeType.Element ) 367 { 368 string configName = reader.GetAttribute( "name" ); 369 currentConfig = new ProjectConfig( configName ); 370 currentConfig.BasePath = reader.GetAttribute( "appbase" ); 371 currentConfig.ConfigurationFile = reader.GetAttribute( "configfile" ); 372 373 string binpath = reader.GetAttribute( "binpath" ); 374 currentConfig.PrivateBinPath = binpath; 375 string type = reader.GetAttribute( "binpathtype" ); 376 if ( type == null ) 377 if ( binpath == null ) 378 currentConfig.BinPathType = BinPathType.Auto; 379 else 380 currentConfig.BinPathType = BinPathType.Manual; 381 else 382 currentConfig.BinPathType = (BinPathType)Enum.Parse( typeof( BinPathType ), type, true ); 383 384 string runtime = reader.GetAttribute("runtimeFramework"); 385 if ( runtime != null ) 386 currentConfig.RuntimeFramework = RuntimeFramework.Parse(runtime); 387 388 Configs.Add(currentConfig); 389 if ( configName == activeConfigName ) 390 activeConfig = currentConfig; 391 } 392 else if ( reader.NodeType == XmlNodeType.EndElement ) 393 currentConfig = null; 394 break; 395 396 case "assembly": 397 if ( reader.NodeType == XmlNodeType.Element && currentConfig != null ) 398 { 399 string path = reader.GetAttribute( "path" ); 400 currentConfig.Assemblies.Add( 401 Path.Combine( currentConfig.BasePath, path ) ); 402 } 403 break; 404 405 default: 406 break; 407 } 408 409 this.IsDirty = false; 410 this.reloadRequired = false; 411 } 412 catch( FileNotFoundException ) 413 { 414 throw; 415 } 416 catch( XmlException e ) 417 { 418 throw new ProjectFormatException( 419 string.Format( "Invalid project format: {0}", e.Message ), 420 e.LineNumber, e.LinePosition ); 421 } 422 catch( Exception e ) 423 { 424 throw new ProjectFormatException( 425 string.Format( "Invalid project format: {0} Line {1}, Position {2}", 426 e.Message, reader.LineNumber, reader.LinePosition ), 427 reader.LineNumber, reader.LinePosition ); 428 } 429 finally 430 { 431 reader.Close(); 432 } 433 } 434 435 public void Save() 436 { 437 projectPath = ProjectPathFromFile( projectPath ); 438 439 XmlTextWriter writer = new XmlTextWriter( projectPath, System.Text.Encoding.UTF8 ); 440 writer.Formatting = Formatting.Indented; 441 442 writer.WriteStartElement( "NUnitProject" ); 443 444 if ( configs.Count > 0 || this.BasePath != this.DefaultBasePath ) 445 { 446 writer.WriteStartElement( "Settings" ); 447 if ( configs.Count > 0 ) 448 writer.WriteAttributeString( "activeconfig", ActiveConfigName ); 449 if ( this.BasePath != this.DefaultBasePath ) 450 writer.WriteAttributeString( "appbase", this.BasePath ); 451 if (this.AutoConfig) 452 writer.WriteAttributeString("autoconfig", "true"); 453 if (this.ProcessModel != ProcessModel.Default) 454 writer.WriteAttributeString("processModel", this.ProcessModel.ToString()); 455 if (this.DomainUsage != DomainUsage.Default) 456 writer.WriteAttributeString("domainUsage", this.DomainUsage.ToString()); 457 writer.WriteEndElement(); 458 } 459 460 foreach( ProjectConfig config in Configs ) 461 { 462 writer.WriteStartElement( "Config" ); 463 writer.WriteAttributeString( "name", config.Name ); 464 string appbase = config.BasePath; 465 if ( !PathUtils.SamePathOrUnder( this.BasePath, appbase ) ) 466 writer.WriteAttributeString( "appbase", appbase ); 467 else if ( config.RelativeBasePath != null ) 468 writer.WriteAttributeString( "appbase", config.RelativeBasePath ); 469 470 string configFile = config.ConfigurationFile; 471 if ( configFile != null && configFile != this.ConfigurationFile ) 472 writer.WriteAttributeString( "configfile", config.ConfigurationFile ); 473 474 if ( config.BinPathType == BinPathType.Manual ) 475 writer.WriteAttributeString( "binpath", config.PrivateBinPath ); 476 else 477 writer.WriteAttributeString( "binpathtype", config.BinPathType.ToString() ); 478 479 if (config.RuntimeFramework != null) 480 writer.WriteAttributeString("runtimeFramework", config.RuntimeFramework.ToString()); 481 482 foreach( string assembly in config.Assemblies ) 483 { 484 writer.WriteStartElement( "assembly" ); 485 writer.WriteAttributeString( "path", PathUtils.RelativePath( config.BasePath, assembly ) ); 486 writer.WriteEndElement(); 487 } 488 489 writer.WriteEndElement(); 490 } 491 492 writer.WriteEndElement(); 493 494 writer.Close(); 495 this.IsDirty = false; 496 497 // Once we save a project, it's no longer 498 // loaded as an assembly wrapper on reload. 499 this.isAssemblyWrapper = false; 500 } 501 502 public void Save( string projectPath ) 503 { 504 this.ProjectPath = projectPath; 505 Save(); 506 } 507 #endregion 508 } 509}