cfix cfix 1.3.0 Released, Introducing WinUnit Compatibility

cfix 1.3, the latest version of the unit testing framework for C/C++ on Windows, has just been released. As announced in the last blog post, the major new feature of this release is WinUnit compatibility, i.e. the ability to recompile existing WinUnit test suites into cfix test suites without having to change a single line of code.

To demonstrate that this compatibility indeed works, consider the following simple example:

    #include "WinUnit.h"
    
    <a href="http://www.cfix-testing.org/unit-testing-framework/windows/doc/BEGIN_TEST.html">BEGIN_TEST</a>(DummyTest)
    {
      <a href="http://www.cfix-testing.org/unit-testing-framework/windows/doc/WIN_ASSERT_STRING_EQUAL.html">WIN_ASSERT_STRING_EQUAL</a>( "foo", "bar" "Descriptive message");
    }
    <a href="http://www.cfix-testing.org/unit-testing-framework/windows/doc/BEGIN_TEST.html">END_TEST</a>
    
    

Compile it:

    cl /I %CFIX_HOME%\include /LD /EHa /Zi winunittest.cpp /link /LIBPATH:%CFIX_HOME%\lib\i386
    

Or, in case %INCLUDE% and %LIB% already happen to be set properly:

    cl /LD /EHa winunittest.cpp
    

Note that the only difference to compiling the test for WinUnit ist that a different include path is used – rather than the original winunit.h, cfix’ own winunit.h is used, which in turn implements the WinUnit API on top of the existing API.

The resulting DLL is a valid cfix DLL and its tests can be run in the usual manner. As the example contains a failing test, cfix will print the stack trace and error description to the console:

    D:\sample>cfix32 -ts -z winunittest.dll
    cfix version 1.3.0.3340 (fre)
    (C) 2008-2009 - Johannes Passing - http://www.cfix-testing.org/
    [Failure]      winunittest.DummyTest.DummyTest
          winunittest.cpp(5): DummyTest
    
          Expression: Descriptive message: [foo] == [bar] (Expression: "foo" == "bar")
          Last Error: 0 (The operation completed successfully. )
    
          cfix!CfixpCaptureStackTrace +0x40
          cfix!CfixPeReportFailedAssertion +0xd2
          winunittest!cfixcc::Assertion::Fail<std::...
          winunittest!cfixcc::Assertion::Relate<std...
          winunittest!cfixcc::Assertion::Relate ...
          winunittest!cfixcc::Assertion::RelateStri...
          winunittest!DummyTest +0x9c
          cfix!CfixsRunTestRoutine +0x33
          cfix!CfixsRunTestCaseMethod +0x27
          cfix!CfixsRunTestCase +0x25
          ...
    

Of course, cfix also supports WinUnit fixtures, as the following example, taken from the original WinUnit article on MSDN demonstrates:

    #include "WinUnit.h"
    #include <windows.h>
    
    // Fixture must be declared.
    <a href="http://www.cfix-testing.org/unit-testing-framework/windows/doc/FIXTURE.html">FIXTURE</a>(DeleteFileFixture);
    
    namespace
    {
      TCHAR s_tempFileName[MAX_PATH] = _T("");
      bool IsFileValid(TCHAR* fileName);
    }
    
    // Both SETUP and TEARDOWN must be present. 
    <a href="http://www.cfix-testing.org/unit-testing-framework/windows/doc/SETUP.html">SETUP</a>(DeleteFileFixture)
    {
      // This is the maximum size of the directory passed to GetTempFileName.
      const unsigned int maxTempPath = MAX_PATH - 14; 
      TCHAR tempPath[maxTempPath + 1] = _T("");
      DWORD charsWritten = GetTempPath(maxTempPath + 1, tempPath);
      // (charsWritten does not include null character)
      <a href="http://www.cfix-testing.org/unit-testing-framework/windows/doc/WIN_ASSERT_TRUE.html">WIN_ASSERT_TRUE</a>(charsWritten  0, 
        _T("GetTempPath failed."));
    
      // Create a temporary file
      UINT tempFileNumber = GetTempFileName(tempPath, _T("WUT"), 
        0, // This means the file will get created and closed.
        s_tempFileName);
    
      // Make sure that the file actually exists
      WIN_ASSERT_WINAPI_SUCCESS(IsFileValid(s_tempFileName), 
        _T("File %s is invalid or does not exist."), s_tempFileName);
    }
    
    // TEARDOWN does the inverse of SETUP, as well as undoing 
    // any side effects the tests could have caused.
    <a href="http://www.cfix-testing.org/unit-testing-framework/windows/doc/TEARDOWN.html">TEARDOWN</a>(DeleteFileFixture)
    {
      // Delete the temp file if it still exists.
      if (IsFileValid(s_tempFileName))
      {
        // Ensure file is not read-only
        DWORD fileAttributes = GetFileAttributes(s_tempFileName);
        if (fileAttributes & FILE_ATTRIBUTE_READONLY)
        {
          WIN_ASSERT_WINAPI_SUCCESS(
            SetFileAttributes(s_tempFileName, 
              fileAttributes ^ FILE_ATTRIBUTE_READONLY),
              _T("Unable to undo read-only attribute of file %s."),
              s_tempFileName);
        }
    
        // Since I'm testing DeleteFile, I use the alternative CRT file
        // deletion function in my cleanup.
        <a href="http://www.cfix-testing.org/unit-testing-framework/windows/doc/WIN_ASSERT_ZERO.html">WIN_ASSERT_ZERO</a>(_tremove(s_tempFileName), 
          _T("Unable to delete file %s."), s_tempFileName);
      }
    
      // Clear the temp file name.
      ZeroMemory(s_tempFileName, 
        ARRAYSIZE(s_tempFileName) * sizeof(s_tempFileName[0]));
    }
    
    <a href="http://www.cfix-testing.org/unit-testing-framework/windows/doc/BEGIN_TESTF.html">BEGIN_TESTF</a>(DeleteFileShouldDeleteFileIfNotReadOnly, DeleteFileFixture)
    {
      WIN_ASSERT_WINAPI_SUCCESS(DeleteFile(s_tempFileName));
      <a href="http://www.cfix-testing.org/unit-testing-framework/windows/doc/WIN_ASSERT_FALSE.html">WIN_ASSERT_FALSE</a>(IsFileValid(s_tempFileName), 
        _T("DeleteFile did not delete %s correctly."),
        s_tempFileName);
    }
    <a href="http://www.cfix-testing.org/unit-testing-framework/windows/doc/BEGIN_TESTF.html">END_TESTF</a>
    
    <a href="http://www.cfix-testing.org/unit-testing-framework/windows/doc/BEGIN_TESTF.html">BEGIN_TESTF</a>(DeleteFileShouldFailIfFileIsReadOnly, DeleteFileFixture)
    {
      // Set file to read-only
      DWORD fileAttributes = GetFileAttributes(s_tempFileName);
      WIN_ASSERT_WINAPI_SUCCESS(
        SetFileAttributes(s_tempFileName, 
        fileAttributes | FILE_ATTRIBUTE_READONLY));
    
      // Verify that DeleteFile fails with ERROR_ACCESS_DENIED
      // (according to spec)
      <a href="http://www.cfix-testing.org/unit-testing-framework/windows/doc/WIN_ASSERT_FALSE.html">WIN_ASSERT_FALSE</a>(DeleteFile(s_tempFileName));
      <a href="http://www.cfix-testing.org/unit-testing-framework/windows/doc/WIN_ASSERT_EQUAL.html">WIN_ASSERT_EQUAL</a>(ERROR_ACCESS_DENIED, GetLastError());
    }
    <a href="http://www.cfix-testing.org/unit-testing-framework/windows/doc/BEGIN_TESTF.html">END_TESTF</a>
    
    namespace
    {
      bool IsFileValid(TCHAR* fileName)
      {
        return (GetFileAttributes(fileName) != INVALID_FILE_ATTRIBUTES);
      }
    }
    

Compiling and running this test yields the expected output:

    d:\sample>cl /I %CFIX_HOME%\include /LD /EHa /Zi fixture.cpp /link /LIBPATH:%CFIX_HOME%\lib\i386
    d:\sample>cfix32 -ts -z fixture.dll
    cfix version 1.3.0.3340 (fre)
    (C) 2008-2009 - Johannes Passing - http://www.cfix-testing.org/
    [Success]      fixture.DeleteFileFixture.DeleteFileShouldDeleteFileIfNotReadOnly
    [Success]      fixture.DeleteFileFixture.DeleteFileShouldFailIfFileIsReadOnly
    
    
           1 Fixtures
           2 Test cases
               2 succeeded
               0 failed
               0 inconclusive
    

Limitations

All compatibility has its limitations – although cfix supports all major WinUnit constructs and assertions, there are a small number of known limitations, which are listed in the documentation. And although I am confident that most WinUnit code should compile and run just fine, it is, of course, possible, that further limitations pop up. In such cases, I would welcome an appropriate bug report and will try to fix cfix accordingly.

Documentation

In order to have cfix be a fully adequate replacement for cfix, the cfix documentation has additionally been augmented to include a documentation of the entire WinUnit API.

Technical background

Technically, implementing the compatibility layer went rather smoothly. On the one hand, WinUnit and cfix have similar architectures, which makes many things easier. On the other hand, WinUnit has a clean, single public header file (contrast that to CppUnit!), which also simplified things. And as WinUnit is limited to C++, I was able to use C++ templates (in combination with some preprocessor macros) to implement the entire WinUnit compatibility layer without having to change a single line of cfix itself. Rather, the WinUnit macros/classes are all mapped onto the existing cfix C++ API, which already includes most of what was neccessary to implement the WinUnit functionality.

Conclusion

In case have been using WinUnit the past and have a set of existing WinUnit-based test suites, give cfix a try – Not only should it be a full-featured replacement for WinUnit, you can also expect to see, and benefit from new features in upcoming releases!

Last but not least, the release contains a number of minor bugfixes. So upgrading is recommended even if you do not intend to use the new WinUnit compatibility feature.

cfix can be downloaded here.

Any opinions expressed on this blog are Johannes' own. Refer to the respective vendor’s product documentation for authoritative information.
« Back to home