Thursday, February 23, 2012

Unit Test looking for missing files in your project


When you're working with a big team of developers and a nice Build Server, a lot of times you rather work on a separate feature branch not to break the build while committing. This is very comfortable since you have your own "work zone" and don't bother anybody else, nor have to worry about breaking the build until you're done with your feature and ready to reintegrate to the trunk (or your main working branch).

But this comes with a price...
It's not always easy dealing with all the conflicts when reintegrating your branch, and sometimes this can lead to problems. One of the main problems we ran into a couple of times recently at my work place while doing this process was that while reintegrating, we missed some new files that were added to the project while dealing with the csproj file conflicts.

This means we had new files added to the project, and were in the project directory, but not in the '.csproj' file. This might be a class that is referenced throughout the project, which in this case you're lucky and the project won't compile, which will immediately let you know there was a problem. But if you're not lucky, this will be a controller class file, or an 'aspx' file, and leaving it out will never cause a problem for the build server (if your project isn't 100% covered with tests - a totally different subject for a whole new post!).

So... what's the easiest way to quickly solve this problem ? Write a test that will check this! :)
That's exactly what I recently did, and now every time this happens the build server will fail, with a nice message that shows you all the files you left out!
(of course, you need to make sure you didn't by accidentally leave this test out of the csproj!! :)

For your unit testing pleasures -

[Test]
public void CheckingThatAllFilesAreInCsProj()
{
    const string projectName = "TestWebsite";
    var projectDirectory = GetProjectDirectory(projectName);
    var projMissingFiles = RetrieveMissingFilesList(projectDirectory.FullName, projectName);

    Assert.IsTrue(projMissingFiles.Count() == 0,
        "There are files that are missing in the .csproj file : \n" + string.Join("\n ", projMissingFiles));
}

private DirectoryInfo GetProjectDirectory(string projectName)
{
    var currentDirectory = new DirectoryInfo(Directory.GetCurrentDirectory());
    while (!currentDirectory.GetDirectories("*", SearchOption.TopDirectoryOnly).Any(x => x.Name == projectName))
    {
        currentDirectory = currentDirectory.Parent;
    }
    return new DirectoryInfo(currentDirectory.FullName + "\\" + projectName);
}

private string[] RetrieveMissingFilesList(string projectDirectory, string projectName)
{
    var csFiles = Directory.GetFiles(projectDirectory, "*.cs", SearchOption.AllDirectories);
    var aspxFiles = Directory.GetFiles(projectDirectory, "*.aspx", SearchOption.AllDirectories);
    var projFiles = csFiles.Union(aspxFiles);

    var csprojDocument = XDocument.Load(projectDirectory + "\\" + projectName + ".csproj");
    var csFileElements = GetCsFileElements(csprojDocument);
    var contentFileElements = GetContentFileElements(csprojDocument);
    var allFileElements = csFileElements.Union(contentFileElements);

    var projMissingFiles = new List<string>();
    foreach (var file in projFiles)
    {
        var stripFileName = file.Replace(projectDirectory, "");

        if (!allFileElements.Any(x => x.Attribute("Include").Value.ToLower() == file.Replace(projectDirectory + "\\", "").ToLower()))
            projMissingFiles.Add(stripFileName);
    }

    return projMissingFiles.ToArray();
}

private static IEnumerable<XElement> GetContentFileElements(XDocument csprojDocument)
{
    return GetFileElements(csprojDocument, "Content");
}

private static IEnumerable<XElement> GetCsFileElements(XDocument csprojDocument)
{
    return GetFileElements(csprojDocument, "Compile");
}

private static IEnumerable<XElement> GetFileElements(XDocument csprojDocument, string elementType)
{
    return csprojDocument.Elements()
        .Where(x => x.Name.LocalName == "Project").Elements()
        .Where(x => x.Name.LocalName == "ItemGroup" &&
            x.HasElements &&
            x.Elements().Where(t => t.Name.LocalName == elementType).Count() > 0).Elements();
}


The code in the test isn't the most beautiful thing I wrote but it does serve a great purpose.
All it does is search the project folder for all .cs and .aspx files, and then dissects the csproj file (which is just a big xml file) to find all the file references.
The code I posted just checks for cs/aspx files, but you can easily add functionality for js, css, html and any other file type you like...

Enjoy :)

No comments:

Post a Comment