// NAnt - A .NET build tool
// Copyright (C) 2001-2002 Gerry Shaw
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
// Eric Gunnerson
// Gerry Shaw (gerry_shaw@yahoo.com)
using System;
using System.IO;
using System.Reflection;
using System.Collections;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Globalization;
using NUnit.Framework;
using NAnt.Core;
namespace Tests.NAnt.Core {
///
/// General purpose test that checks to see that all exceptions implement
/// required methods.
///
///
/// This class was inspired / stolen from the article "The Well-Tempered Exception",
/// by Eric Gunnerson, Microsoft.
///
/// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp08162001.asp
///
/// The test verifies:
/// 1) The exception name ends in "Exception"...
/// 2) The exception implements the 3 standard constructors:
/// class();
/// class(string message);
/// class(string message, Exception inner);
/// 3) The exception implements the deserialization constructor:
/// class(SerializationInfo info, StreamingContext context);
/// 4) The exception has no public fields
/// 5) If the exception has private fields, that it implements GetObjectData()
/// (there's no guarantee it does it *correctly*)
/// 6) If the exception has private fields, it overrides the Message property.
/// 7) The exception is marked as serializable.
///
[TestFixture]
public class ExceptionTest {
#region Public Instance Methods
[Test]
public void Test_AllExceptions() {
// For each assembly we want to check instantiate an object from
// that assembly and use the type to get the assembly.
// NAnt.Core.dll
ProcessAssembly(Assembly.GetAssembly(typeof(Project)));
// Check the test exceptions to make sure test is valid - see bottom
// of this file.
ProcessAssembly(Assembly.GetAssembly(this.GetType()));
}
public void ProcessAssembly(Assembly a) {
foreach (Type t in a.GetTypes()) {
if (IsException(t)) {
CheckException(a, t);
}
}
}
#endregion Public Instance Methods
#region Private Instance Methods
private bool IsException(Type type) {
Type baseType = null;
while ((baseType = type.BaseType) != null) {
if (baseType == typeof(System.Exception)) {
return true;
}
type = baseType;
}
return false;
}
private void CheckPublicConstructor(Type t, string description, params Type[] parameters) {
// locate constructor
ConstructorInfo ci = t.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, parameters, null);
// fail if constructor does not exist
Assert.IsNotNull(ci, t.Name + description + " is a required constructor.");
// fail if constructor is private
Assert.IsFalse(ci.IsPrivate, t.Name + description + " is private, must be public.");
// fail if constructor is protected
Assert.IsFalse(ci.IsFamily, t.Name + description + " is internal, must be public.");
// fail if constructor is internal
Assert.IsFalse(ci.IsAssembly, t.Name + description + " is internal, must be public.");
// fail if constructor is protected internal
Assert.IsFalse(ci.IsFamilyOrAssembly, t.Name + description + " is protected internal, must be public.");
// sanity check to make sure the constructor is public
Assert.IsTrue(ci.IsPublic, t.Name + description + " is not public, must be public.");
}
private void CheckProtectedConstructor(Type t, string description, params Type[] parameters) {
// locate constructor
ConstructorInfo ci = t.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, parameters, null);
// fail if constructor does not exist
Assert.IsNotNull(ci, t.Name + description + " is a required constructor.");
// fail if constructor is public
Assert.IsFalse(ci.IsPublic, t.Name + description + " is public, must be protected.");
// fail if constructor is private
Assert.IsFalse(ci.IsPrivate, t.Name + description + " is private, must be public or protected.");
// fail if constructor is internal
Assert.IsFalse(ci.IsAssembly, t.Name + description + " is internal, must be protected.");
// fail if constructor is protected internal
Assert.IsFalse(ci.IsFamilyOrAssembly, t.Name + description + " is protected internal, must be protected.");
// sanity check to make sure the constructor is protected
Assert.IsTrue(ci.IsFamily, t.Name + description + " is not protected, must be protected.");
}
private void CheckPrivateConstructor(Type t, string description, params Type[] parameters) {
// locate constructor
ConstructorInfo ci = t.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, parameters, null);
// fail if constructor does not exist
Assert.IsNotNull(ci, t.Name + description + " is a required constructor.");
// fail if constructor is public
Assert.IsFalse(ci.IsPublic, t.Name + description + " is public, must be private.");
// fail if constructor is protected
Assert.IsFalse(ci.IsFamily, t.Name + description + " is protected, must be private.");
// fail if constructor is internal
Assert.IsFalse(ci.IsAssembly, t.Name + description + " is internal, must be private.");
// fail if constructor is protected internal
Assert.IsFalse(ci.IsFamilyOrAssembly, t.Name + description + " is protected internal, must be private.");
// sainty check to make sure the constructor is private
Assert.IsTrue(ci.IsPrivate, t.Name + description + " is not private, must be private.");
}
private void CheckException(Assembly assembly, Type t) {
// check to see that the exception is correctly named, with "Exception" at the end
t.Name.EndsWith("Exception");
Assert.IsTrue(t.Name.EndsWith("Exception"), t.Name + " class name must end with Exception.");
// Does the exception have the 3 standard constructors?
// Default constructor
CheckPublicConstructor(t, "()");
// Constructor with a single string parameter
CheckPublicConstructor(t, "(string message)", typeof(System.String));
// Constructor with a string and an inner exception
CheckPublicConstructor(t, "(string message, Exception inner)",
typeof(System.String), typeof(System.Exception));
// check to see if the serialization constructor is present
// if exception is sealed, constructor should be private
// if exception is not sealed, constructor should be protected
if (t.IsSealed) {
// check to see if the private serialization constructor is present...
CheckPrivateConstructor(t, "(SerializationInfo info, StreamingContext context)",
typeof(System.Runtime.Serialization.SerializationInfo),
typeof(System.Runtime.Serialization.StreamingContext));
} else {
// check to see if the protected serialization constructor is present...
CheckProtectedConstructor(t, "(SerializationInfo info, StreamingContext context)",
typeof(System.Runtime.Serialization.SerializationInfo),
typeof(System.Runtime.Serialization.StreamingContext));
}
// check to see if the type is market as serializable
Assert.IsTrue(t.IsSerializable, t.Name + " is not serializable, missing [Serializable]?");
// check to see if there are any public fields. These should be properties instead...
FieldInfo[] publicFields = t.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance);
if (publicFields.Length != 0) {
foreach (FieldInfo fieldInfo in publicFields) {
Assert.Fail(t.Name + "." + fieldInfo.Name + " is a public field, should be exposed through property instead.");
}
}
// If this exception has any fields, check to make sure it has a
// version of GetObjectData. If not, it does't serialize those fields.
FieldInfo[] fields = t.GetFields(BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
if (fields.Length != 0) {
if (t.GetMethod("GetObjectData", BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance) == null) {
Assert.Fail(t.Name + " does not implement GetObjectData but has private fields.");
}
// Make sure Message is overridden if there are private fields.
// drieseng : commented out this test, as it does not always
// make sense. Not all private fields should somehow be exposed
// as part of the message of the exception.
//Assert.IsTrue(t.GetProperty("Message", BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance) != null, t.Name + " does not override the Message property.");
}
}
#endregion Private Instance Methods
}
/// Do nothing exception to verify that the exception tester is working correctly.
[Serializable]
public class SimpleTestException : ApplicationException {
#region Public Instance Constructors
public SimpleTestException() {
}
public SimpleTestException(string message) : base(message) {
}
public SimpleTestException(string message, Exception inner) : base(message, inner) {
}
#endregion Public Instance Constructors
#region Protected Instance Constructors
// deserialization constructor
protected SimpleTestException(SerializationInfo info, StreamingContext context) : base(info, context) {
}
#endregion Protected Instance Constructors
}
///
/// Exception to verify that the exception tester is working correctly.
///
[Serializable]
public class TestException : ApplicationException, ISerializable {
#region Private Instance Fields
private int _value;
#endregion Private Instance Fields
#region Public Instance Constructors
public TestException() {
}
public TestException(string message) : base(message) {
}
public TestException(string message, Exception inner) : base(message, inner) {
}
// constructors that take the added value
public TestException(string message, int value) : base(message) {
_value = value;
}
#endregion Public Instance Constructors
#region Protected Instance Constructors
// deserialization constructor
protected TestException(SerializationInfo info, StreamingContext context) : base(info, context) {
_value = info.GetInt32("Value");
}
#endregion Protected Instance Constructors
#region Public Instance Properties
public int Value {
get { return _value; }
}
#endregion Public Instance Properties
#region Override implementation of ApplicationException
// Called by the frameworks during serialization
// to fetch the data from an object.
public override void GetObjectData(SerializationInfo info, StreamingContext context) {
base.GetObjectData(info, context);
info.AddValue("Value", _value);
}
// overridden Message property. This will give the
// proper textual representation of the exception,
// with the added field value.
public override string Message {
get {
// NOTE: should be localized...
string s = String.Format(CultureInfo.InvariantCulture, "Value: {0}", _value);
return base.Message + Environment.NewLine + s;
}
}
#endregion Override implementation of ApplicationException
}
///
/// Exception to verify that the exception tester is working on sealed exception.
///
[Serializable]
public sealed class SealedTestException : TestException {
#region Public Instance Constructors
public SealedTestException() {
}
public SealedTestException(string message) : base(message) {
}
public SealedTestException(string message, Exception inner) : base(message, inner) {
}
// constructors that take the added value
public SealedTestException(string message, int value) : base(message, value) {
}
#endregion Public Instance Constructors
#region Private Instance Constructors
// deserialization constructor
private SealedTestException(SerializationInfo info, StreamingContext context) : base(info, context) {
}
#endregion Private Instance Constructors
}
}