Jan 30 2008

Incorrect code coverage references in testrunconfig

Category: .NetRory Primrose @ 04:59

I have been having a problem with testrunconfig files recently.

Lets say I have developed a project with which I also have a unit test project in the solution. I have done this using the Debug build configuration. I set up the code coverage and everything is fine. I then make some changes to the code and also happen to change the build configuration. If I rerun the unit tests, Visual Studio starts freaking out.

When you select an assembly for code coverage, a relative path to that assembly is stored in the testrunconfig file. The issue is that the relative is specific to a build configuration. In this case, the testrunconfig points code coverage to the assemblies in bin\Debug rather than the path related to my current build configuration. But I have changed the code so that the assemblies in bin\Debug are out of date and don't match the code being executed by the unit test under the current build configuration.

After talking to Microsoft about it, their workaround was to replace bin\Debug with the %OutDir% macro in the relative paths in the testrunconfig file. This macro gets evaluated and the correct build configuration path gets added into the path when the assembly path is resolved. This is not supported in the IDE so you will have to open the testrunconfig using the xml editor and modify the relative path by hand.

Tags:

Jan 16 2008

LogLevel NAnt Task

Category: Applications | .NetRory Primrose @ 10:53

An ongoing issue that I am having with NAnt scripts is bloat in the log records being written to the screen and to disk. Tasks like xmlpeek and regex log messages that I am not interested in for the given logging threshold. It would be nice to not have these unwanted messages logged but for everything else to be logged as expected given the defined log threshold.

After doing some searching, I came across this post from Jay Flowers. Will Buttitta posted another version of the same idea in a comment against that post. Using tasks to change the logging threshold at runtime for a particular part of the script is a great idea. Unfortunately, I have found that neither of these implementations work.

After going through Reflector, I have found that classes that are derived from Task and call the Log method end up invoking Element.Log() which calls straight down to Project.Log(). The Project.Log method does not check the logging threshold before writing the log entry. What I haven't been able to figure out is why Task.Log is not invoked. If it was, then I think the two solutions in Jay's post would probably work as Task.Log() checks the current logging threshold before calling down to the base class.

There is however another way that will correctly modify the logging threshold at a much lower level.

Project.Log() invokes OnMessageLogged(). After searching for what is hooked up to the MessageLogged event, I found that a default listener is created and hooked up via Project.CreateDefaultLogger(). That pointed me to look at the Project.BuildListeners property and the IBuildLogger interface. The IBuildLogger has its own Threshold property.

This opens up a very simple solution. Regardless of how logging is invoked higher up the call chain, this implementation will always correctly define the log threshold at runtime because it is done directly on the loggers. This is of course assuming the logger implementation checks the threshold assigned to it.

using NAnt.Core;
using NAnt.Core.Attributes;
 
namespace Neovolve.NAnt.Tasks
{
    /// <summary>
    /// The <see cref="LogLevelTask"/>
    /// class is a NAnt task that is used to determine the logging level used to execute
    /// a set of child tasks.
    /// </summary>
    /// <remarks>This code was inspired from the blog post found at http://jayflowers.com/WordPress/?p=133</remarks>
    [TaskName("loglevel")]
    public class LogLevelTask : TaskContainer
    {
        #region Declarations
 
        /// <summary>
        /// Stores the LogLevel value.
        /// </summary>
        private Level _logLevel;
 
        #endregion
 
        #region Methods
 
        /// <summary>
        /// Executes the task.
        /// </summary>
        protected override void ExecuteTask()
        {
            Level OldLevel = Project.Threshold;
            AssignLogLevel(LogLevel);

            base.ExecuteTask();

            AssignLogLevel(OldLevel);
        }
 
        /// <summary>
        /// Assigns the log level.
        /// </summary>
        /// <param name="newLevel">The new level.</param>
        private void AssignLogLevel(Level newLevel)
        {
            // Loop through each logger
            foreach (IBuildListener listener in Project.BuildListeners)
            {
                IBuildLogger logger = listener as IBuildLogger;
 
                // Assign the new threshold
                if (logger != null)
                {
                    logger.Threshold = newLevel;
                }
            }
        }
 
        #endregion
 
        #region Properties
 
        /// <summary>
        /// Gets or sets the log level.
        /// </summary>
        /// <value>The log level.</value>
        [TaskAttribute("level", Required = true)]
        public Level LogLevel
        {
            get
            {
                return _logLevel;
            }
            set
            {
                _logLevel = value;
            }
        }
 
        #endregion
    }
}

To change the logging level, add the loglevel task around other tasks and set the level value. For example:

<loglevel level="None">
 
  <!-- Determine whether the template is a project or item related template -->
  <foreach item="Line"
          in="${Path.ProjectFileExpressions}"
          delim=","
          property="pathExpression">
 
    <if test="${ProjectTemplateFile == ''}">
      <regex pattern="${pathExpression}"
            input="${childFile}"
            options="IgnoreCase"
            failonerror="false" />
    </if>
 
  </foreach>
 
</loglevel>

Tags: ,