2012-08-15

Build C++ with MSBuild on Windows by Example


Summary
  • Windows C++ project uses .vcxproj since Visual studio 2010. 
  • Visual Studio 2010 uses MSBuild to build C++ project.
  • The .vcxproj file can be created manually, and you can import common project settings from separate .props files.
  • Some sections of the .vcxproj file have to be in particular order.
  • If open .vcxproj file in Visual Studio and enable 'show all files', it will show the folder structure in the same folder as .vcxproj sits. Files outside of the folder is listed without folder structure.
  • It seems when using relative path to include .cpp or .h files for 'ClInclude' or 'ClCompile', it slows down the Visual Studio very very badly to open the project.
Build the project from command line
   
# Set the environment for VS
%VS100COMNTOOLS%\vsvars32.bat

# Build the project
msbuild xxx.vcxproj /p:Configuration=Debug
Example of .vcxproj file

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!-- Set the project name and the relative path to the Application folder -->
  <PropertyGroup>
   <ProjectName>Access</ProjectName>
   <TopDir>.</TopDir>
  </PropertyGroup>

  <!-- The main project file has to include this part -->
  <ItemGroup Label="ProjectConfigurations">
    <ProjectConfiguration Include="Debug|Win32">
      <Configuration>Debug</Configuration>
      <Platform>Win32</Platform>
    </ProjectConfiguration>
    <ProjectConfiguration Include="Release|Win32">
      <Configuration>Release</Configuration>
      <Platform>Win32</Platform>
    </ProjectConfiguration>
  </ItemGroup>

  <!-- Include the common settings -->
  <Import Project="$(TopDir)\common.props" />

  <!-- Include all the .cpp files here -->
  <ItemGroup>
    <ClCompile Include="App\Access\**\*.cpp" />
    <ClCompile Include="Lib\**\*.cpp" />
  </ItemGroup>

  <!-- Include all the .h files here -->
  <ItemGroup>
    <ClInclude Include="App\Access\**\*.h" />
    <ClInclude Include="Lib\Inc\**\*.h" />
    <ClInclude Include="Lib\Gateway\**\*.h" />
  </ItemGroup>

  <!-- Include CPP defaults -->
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
  <ImportGroup Label="ExtensionTargets" />
</Project>

The common settings are in .props file (common.props):
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <PropertyGroup Label="Globals">
    <Keyword>Win32Proj</Keyword>
  </PropertyGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>true</UseDebugLibraries>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
    <ConfigurationType>Application</ConfigurationType>
    <UseDebugLibraries>false</UseDebugLibraries>
  </PropertyGroup>
  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
  <ImportGroup Label="ExtensionSettings">
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  </ImportGroup>
  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
  </ImportGroup>
  <PropertyGroup Label="UserMacros" />
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <LinkIncremental>true</LinkIncremental>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <LinkIncremental>true</LinkIncremental>
  </PropertyGroup>

  <PropertyGroup>
   
   <TargetName Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectName)</TargetName>
   <TargetName Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectName)d</TargetName>

   <OutDir>$(TopDir)\Build\win32\</OutDir>
   <IntDir>$(TopDir)\Build\win32\$(ProjectName)-$(Configuration)\</IntDir>

   <PocoDir>$(TopDir)\..\Poco</PocoDir>
   <PocoSrcDir>$(PocoDir)\poco-1.4.3p1-all</PocoSrcDir>
   <PocoLibDir>$(PocoSrcDir)\lib</PocoLibDir>
   <PocoBinDir>$(PocoSrcDir)\bin</PocoBinDir>
   
   <OpenSSLDir>$(PocoDir)\OpenSSL-Win32</OpenSSLDir>
   <OpenSSLIncDir>$(OpenSSLDir)\include</OpenSSLIncDir>
   <OpenSSLBinDir>$(OpenSSLDir)\bin</OpenSSLBinDir>

   <CppUnitDir>$(TopDir)\..\CppUnit</CppUnitDir>
   <CppUnitSrcDir>$(CppUnitDir)\cppunit-1.12.1</CppUnitSrcDir>
   <CppUnitLibDir>$(CppUnitDir)\Build\win32</CppUnitLibDir>
   <CppUnitLib Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(CppUnitLibDir)\CppUnit.lib</CppUnitLib> 
   <CppUnitLib Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(CppUnitLibDir)\CppUnitd.lib</CppUnitLib> 

    <AppIncludes>
      $(PocoSrcDir)\Foundation\include;
      $(PocoSrcDir)\Crypto\include;
      $(PocoSrcDir)\Data\include;
      $(PocoSrcDir)\Data\SQLite\include;
      $(PocoSrcDir)\Net\include;
      $(PocoSrcDir)\Util\include;
      $(PocoSrcDir)\XML\include;
      $(PocoSrcDir)\Zip\include;
      $(PocoSrcDir)\NetSSL_OpenSSL\include;
      $(AppIncludes)
    </AppIncludes>

    <AppIncludes>
      $(CppUnitSrcDir)\include;
      $(AppIncludes)
    </AppIncludes>
    
    <AppIncludes>
      $(OpenSSLIncDir);
      $(AppIncludes)
    </AppIncludes>
    
    <AppIncludes>
      $(TopDir)\Lib\Gateway\Framework\External;
      $(TopDir)\Lib\Gateway\Dingo\Gateway\Siemens.Gateway.Linux.Client;
      $(TopDir)\Lib\Gateway\Dingo\Gateway\Siemens.Gateway.Linux.Tools;
      $(TopDir)\Lib\Gateway\Dingo\Gateway\PlatformSpecific\Linux;
      $(TopDir)\Lib\Gateway\Dingo\Client\AccessPoint\GeneratedServices;
      $(AppIncludes)
    </AppIncludes>
    
    <AppIncludes>
      $(TopDir)\Lib;
      $(TopDir)\Lib\Inc;
      $(TopDir)\App\Access;
      $(TopDir)\App\Access\Inc;
      $(TopDir)\App\Diag;
      $(TopDir)\App\Diag\Inc;
      $(TopDir)\App\Supervisor;
      $(TopDir)\App\Supervisor\Inc;
      $(TopDir)\Test\UnitTest;
      $(TopDir)\Test\UnitTest\Lib;
      $(TopDir)\Test\UnitTest\App\Access;
      $(TopDir)\Test\UnitTest\App\Diag;
      $(TopDir)\Test\UnitTest\App\Supervisor;
      $(AppIncludes);
    </AppIncludes>
    
    <AppLibDirs>
      $(PocoLibDir);
      $(PocoBinDir);
      $(OpenSSLBinDir);
      $(OutDir);
      $(AppLibDirs);
    </AppLibDirs>
<AppStaticLibs>
 $(CppUnitLib);
</AppStaticLibs>   
  </PropertyGroup>

  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <ClCompile>
      <AdditionalIncludeDirectories>$(AppIncludes);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_WINDOWS;WINVER=0x0500;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
      <WarningLevel>Level3</WarningLevel>
      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
      <Optimization>Disabled</Optimization>
    </ClCompile>
    <Link>
      <AdditionalLibraryDirectories>$(AppLibDirs);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
 <AdditionalDependencies>$(AppStaticLibs);%(AdditionalDependencies)</AdditionalDependencies>      
      <TargetMachine>MachineX86</TargetMachine>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <SubSystem>Console</SubSystem>
    </Link>
  </ItemDefinitionGroup>
  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    <ClCompile>
      <AdditionalIncludeDirectories>$(AppIncludes);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;_WINDOWS;WINVER=0x0500;%(PreprocessorDefinitions)</PreprocessorDefinitions>
      <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
      <WarningLevel>Level3</WarningLevel>
      <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
    </ClCompile>
    <Link>
      <AdditionalLibraryDirectories>$(AppLibDirs);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
 <AdditionalDependencies>$(AppStaticLibs);%(AdditionalDependencies)</AdditionalDependencies>      <TargetMachine>MachineX86</TargetMachine>
      <GenerateDebugInformation>true</GenerateDebugInformation>
      <SubSystem>Console</SubSystem>
      <EnableCOMDATFolding>true</EnableCOMDATFolding>
      <OptimizeReferences>true</OptimizeReferences>
    </Link>
  </ItemDefinitionGroup>
  
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    <LocalDebuggerEnvironment>PATH=$(PocoBinDir)$(LocalDebuggerEnvironment)</LocalDebuggerEnvironment>
    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
  </PropertyGroup>
  
  <PropertyGroup>
    <ShowAllFiles>true</ShowAllFiles>
  </PropertyGroup>
      
</Project>

2012-06-21

C++ Class Member Function Pointer

#include <string>
#include <iostream>
using namespace std;

class A {
public:
void foo(string s)
{
cout << "A::foo() " << s << endl;
}

void foo2()
{
cout << "A::foo2()" << endl;
}
};

class B {
public:
virtual void foo(string s) = 0;
};

class B1 : public B {
public:
virtual void foo(string s)
{
cout << "B1::foo() " << s << endl;
}
};

class B2 : public B1 {
public:
virtual void foo(string s)
{
cout << "B2::foo() " << s << endl;
}
};


typedef void (*callback1)(string); // normal function pointer
typedef void (A::*callback2)(string); // class A member function pointer
typedef void (B::*callback3)(string); // class B member function pointer
typedef void (A::*callback4)(); // class A member function pointer

int main()
{
// normal function pointer and member function pointer size are different
cout << "size of callback1: " << sizeof(callback1) << endl;
cout << "size of &B::foo  : " << sizeof(&B::foo) << endl;
cout << "size of &B1::foo : " << sizeof(&B1::foo) << endl;
cout << "size of &B2::foo : " << sizeof(&B2::foo) << endl;

A* a1 = new A;
A  a2;
callback2 f1 = &A::foo;

// need an object to call member function
(a1->*f1)("from pointer");
(a2.*f1)("from reference");

B1 b1;
B2 b2;

// use base class' member callback call sub-class
callback3 f2 = (callback3)&B1::foo;
(b1.*f2)("b1");

// use base class' member callback call sub-class
f2 = (callback3)&B2::foo;
(b2.*f2)("b2");

// even can call other class's member function with same signature
f2 = (callback3)&A::foo;
(b1.*f2)("b1");
(b2.*f2)("b2");
// (a2.*f2)("a2"); // can't compile. the object has to be the same type of the callback class

// even can call other class's member function with different signature
f2 = (callback3)&A::foo2;
(b1.*f2)("b1");

// another example to call other class's member function with different signature
callback4 f4 = (callback4)&A::foo;
(a2.*f4)();

// can't assign member function pointer to normal function pointer
// callback1 f3 = (callback1)&A::foo; // can't compile


return 0;
}

/*
 * Compile with g++ on Linux.
 *
 * Output of the program:

size of callback1: 4
size of &B::foo  : 8
size of &B1::foo : 8
size of &B2::foo : 8
A::foo() from pointer
A::foo() from reference
B1::foo() b1
B2::foo() b2
A::foo() b1
A::foo() b2
A::foo2()
A::foo()


 */

面向对象设计原则

摘自《设计模式精解》, 21.2 “面向对象原则的总结”


  • “对象”是负有定义良好的责任的东西
  • 对象对自己负责
  • “封装”意味着任何形式的隐藏:
    • 数据隐藏
    • 类隐藏(藏在抽象类或接口后面)
    • 实现隐藏
  • 使用共同点/变化点分析抽象出行为和数据中的变化点
  • 针对接口进行设计
  • 把继承考虑为一种封装变化的办法,而不是为现有对象制造特殊情况
  • 将变化点封装在一个类中,并使之与这个类中其他的变化点相分离
  • 力求松耦合
  • 力求高内聚
  • 绝对细心地应用“一次并且只有一次”规则

2012-05-24

Set Eclipse Username

Eclipse has ${user} variable which can be used in code template. Normally, it is the name of the operating system user. There are ways to change this to any string you want:

1. From command line

eclipse -vmargs -Duser.name="Aric Wang"

2. From Eclipse Configuration File

Edit 'eclipse.ini' in the installation folder. After line '-vmargs', add a new line like

-Duser.name=Aric Wang

Restart eclipse.

2012-05-23

C++ Unit Test Framework with CppUnit


Introduction

This unit test framework is for all the libraries and applications built within the Application repository. In theory, every class of those libraries and applications should be covered in the unit test.

In order to keep the production code clean, an important rule is that there should be no test related code in the production code. All the unit test code should be separate from the production code, and they should not be built into the production binaries in any case.

CppUnit is used for unit test framework. Poco comes with CppUnit. To build CppUnit library, Poco has to be configured without '--no-tests'.

Below sections will explain the basic concepts of CppUnit and how to use this framework.

CppUnit Concepts


CppUnit has a couple of concepts to organize the unit test, and three of those concepts are needed to understand the Apple AP unit test framework.

Fixture


Fixture is normally a class derived from CppUnit::TestCase. Fixture class is the container of test cases. Each test case is a member function of fixture class. A fixture class can have more than one test cases.

In order to share some common setup between multiple test cases, fixture class can have member variables. Fixture class can override setUp() function to initialize the test environment. tearDown() function can be used to clean up the test environment after execution of test cases.

Test Case


A test case is a public member function of a fixture class. A fixture class can have more than one test cases. Each test case is responsible for testing some features of the object under testing (class or function).

A test case function has a prototype like: void TestXyz(). It normally does some test work of the function Xyz() and check the result with some macros defined by CppUnit::TestCase, i.e. assert(condition). See CppUnit::TestCase for more details.

Test Suite


Test suite is a container of multiple test cases or test suites so that all test cases under it can run at once.

Unit Test Framework

Build Unit Test Suites

All the unit test cases and test suites are built into one application in order to keep the testing code out of the production code. At the top level of Application repository, execute below command to build the unit test:
make unittest
The unit test application is located in the 'Build/$(ARCH)' folder and named 'UnitTest'.

'unittest' is also included in the 'all' target. So another way to build the unit test suites is:
make all

Run Unit Test Suites

To execute the UnitTest application, make sure all related libraries are installed properly. For example, the Poco libraries, including CppUnit library, should be installed properly. If the shared libraries of the Application repository are used by the UnitTest application, they should also be installed properly. If these libraries are not installed, an alternative way is to use LD_LIBRARY_PATH at the command line to specify the pathes of those libraries.

For example, to execute the UnitTest application on x86 Linux platform:
cd Application/Build/i386
LD_LIBRARY_PATH=../../../Poco/poco-1.4.3p1-all/lib/Linux/i686:. ./UnitTest -all
If all test cases succeed, it will list all the test cases, and followed with 'OK (x tests)' at last where x is the number of test cases executed. This is an example:
testzHalt:
testzFive: 
OK (2 tests)
If any test case fails, it will be marked after the name of the test case. A summary of the test result is followed, and then the list of the failures. This is an example:
testzHalt: FAILURE
testzFive:
!!!FAILURES!!!
Runs: 2   Failures: 1   Errors: 0
There was 1 failure:
 1: N7CppUnit10TestCallerI8TestBaseEE.testzHalt
    "false"
    in "/home/one/apple/Application/Test/UnitTest/Lib/Base/TestBase.cpp", line 51
The result code of the UnitTest application can also be used to tell success or failure. If all test cases are successful, the return code will be 0. If any test cases fails, the return code will not be 0.

The UnitTest can run in a couple of formats:
# List all the test suites and test cases in a tree format
UnitTest -print
# Execute all test cases
UnitTest -all  
# Execute one or more test cases.
UnitTest <testcase name> ...   
It is quite flexible to execute a single test case, or a single test suite, or a sub-tree of all the test cases, or even all the test cases.

Basic Classes


The unit test framework mainly deals with two kind of classes: fixture class and test suite class.

Fixture class implements a test fixture, including: setUp(), tearDown(), test cases, and a test suite containing all the test cases. Fixture class is derived from CppUnit::TestCase.

Test suite class is a simple class, which includes a test suite. It is used as a container to organize the fixture classes. Each fixture class's test suite is added to a test suite class, and the test suite class is added to a higher level test suite class. The top level test suite class is TestSuiteTop. All the test suites are organized like a tree. The leave nodes are test cases.

Fixture Class Example

The header file:
#include "CppUnit/TestCase.h" // Base class of fixture class
#include "Base/Base.h"        // The class under test
// Fixture Class Example.
// This class can have member variables in order to set up a environment for
// executing the test cases. In this example, there's no such variables.
//
// This fixture class contains one test case function 'void TestzHalt()', and
// one test suite 'static CppUnit::Test* suite()'.
//
class TestBase: public CppUnit::TestCase
{
public:
    // Return test suite includes all the test cases of this fixture class
    static CppUnit::Test* suite();  
    TestBase(const std::string& name);
    virtual ~TestBase();
    void setUp();       // Set up the environment for executing the test cases
    void tearDown();    // Clean up after execution of test cases
    void TestzHalt();   // Test case for testing Base::zHatl().
                        // More test cases can be added to this fixture.
private:
};
The soucre file:
#include "CppUnit/TestCaller.h"
#include "CppUnit/TestSuite.h"
#include "Base/TestBase.h"
CppUnit::Test* TestBase::suite()
{
    // Create the test suite, and give it a name "TestBase"
    CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("TestBase");
    // Add all test cases to the test suite.
    // pSuite      The test suite object
    // TestBase    The name of the fixture class
    // TestzHalt   The name of the test case function
    CppUnit_addTest(pSuite, TestBase, TestzHalt);
    return pSuite;
}
TestBase::TestBase(const std::string& name): CppUnit::TestCase(name)
{
}

TestBase::~TestBase()
{
}
void TestBase::setUp()
{
    // Set up the environemtn if necessary
}
void TestBase::tearDown()
{
    // clean up the environment after exectuing the test cases.
}
void TestBase::TestzHalt()
{
    // Do some test work on function zHatl().
 
    // Check the result of the test. Normally should check a condition.
    assert(condition);
}
Test Suite Class Example

The header file:
#include "CppUnit/TestSuite.h"
// Test Suite Class Example.
// Test suite class has no test cases, no setUp(), no tearDown(), no member
// variables. It is a container to organize fixture classes.
//
class TestSuiteBase
{
public:
    static CppUnit::Test* suite();    // Return test suite object
};
The source file:
#include "Base/TestSuiteBase.h"
#include "Base/TestBase.h"
CppUnit::Test* TestSuiteBase::suite()
{
    // Create the test suite, and give it name "TestSuiteBase"
    CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("TestSuiteBase");
    // Add fixture class as child test suite. More fixture class can be added.
    // TestBase     The fixture class to be added
    pSuite->addTest(TestBase::suite());
    return pSuite;
}

Code Location

All the unit test code is kept out of the production code, and located under 'Test/UnitTest' folder. Under that folder, similar folder structure is duplicated for source code under 'Lib' and 'App'. Difference is that there's no separate 'Inc' folder for header files. Both header file and source file go to the same folder.

Naming Convention

In order to easily identify the test related code, all the test related files, classes, and test case functions are named in a particular way:

All test suite files are named like TestSuiteModuleName.h/cpp, where 'ModuleName' is the name of the module under test.
All test suite classes are named like TestSuiteModuleName, where 'ModuleName' is the name of the module under test.
All fixture files are named like TestClassName.h/cpp, where 'ClassName' is the name of the class under test.
All fixture classes are named like TestClassName, where 'ClassName' is the name of the class under test.
All test case functions are named like TestFunctionName(), where 'FunctionName' is the name of the function under test.

Test Case Tree

All the test cases are organized into many test suites like a tree. The top level test suites is 'TestSuiteTop'. Each module has a test suite class as a container of all the test suites from that module. Each class has a fixture which includes one or more test cases and a test suite containing all the test cases.

For example, Access application has a 'TestSuiteAccess' which contains all the test suites of its sub-modules. The sub module of Access, i.e. Database, each has a test suite, like TestSuiteDatabase, which contains all the fixtures of its classes. For each class of submodule Database, i.e. class Cardholder, there is a fixture for it, like TestCardholder. The fixture TestCardholder implements all the test cases for class Cardholder, and it also has a test suite to contain all the test cases.

Put the code and test case organization in a tree view:

UnitTest
    |- TestSuiteTop.h/cpp                     - Top level test suite
    |- App
    |   |- Access
    |        |- TestSuiteAccess.h/cpp         - Module test suite
    |        |- Module1
    |        |   |- TestSuiteModule1.h/cpp    - Module test suite
    |        |   |- TestClass1.h/cpp          - Class fixture
    |        |   |- TestClass2.h/cpp          - Class fixture
    |        |- Module2
    |            |- TestSuiteModule2.h/cpp    - Module test suite
    |            |- TestClass3.h/cpp          - Class fixture
    |            |- TestClass4.h/cpp          - Class fixture
    |- Lib
        |- Base
            |- TestSuiteBase.h/cpp            - Module test suite
            |- TestClass5.h/cpp               - Class fixture
            |- TestClass6.h/cpp               - Class fiture

Add Test Case for New Code

If new module, class, or function is added to the production code, you may want to add test cases for it too. There're a couple of things to do:


  1. Create new folder under 'Test/UnitTest' accordingly if necessary.
  2. Create module test suite in the new folder if necessary.
  3. Add the module test suite to its parent module's test suite if necessary.
  4. Create class fixture in the folder for new classes if necessary.
  5. Implement the fixture's test suite if necessary.
  6. Add the fixture's test suite to the module's test suite if necessary.
  7. Implement the test case functions for the new code.

The UnitTest application has to link with the classes under testing. Normally, those classes are out of the 'Test/UnitTest' folder so that they can't be picked up by the makefile automatically. In order to include those classes into the UnitTest application, 'Test/UnitTest/makefile' has to be changed to pick up the code outside 'Test/UnitTest' folder. EXTRA_MODULE can be used for this purpose. For example, to include 'Lib/Base' and 'App/Access/Abc', it has to be included in EXTRA_MODULE like this:

# Extra modules, source code outside of this module
EXTRA_MODULES := \
    $(TOP_DIR)/Lib/Base \
    $(TOP_DIR)/App/Access/Abc

Make sure that the applications main() function is not linked to UnitTest application.

2012-04-11

Linux Thread Scheduling Policy and Priority

Introduction

Linux 2.6.x is pre-emptive kernel. It has 100 level priorities: 0 to 99. 0 is the lowest priority and 99 is the highest.

There're three scheduling policies:

  • SCHED_OTHER
This is default policy. It is a time-slice based policy. All threads of this policy have the same priority 0. The system manage their priorities automatically. User can't set priority of this kind of thread.
  • SCHED_FIFO
This is a real-time scheduling. The high priority thread always pre-empt the lower priority threads. The threads with the same priority run in a FIFO mode, which means the thread has to give up the CPU otherwise other threads can't run.
  • SCHED_RR
This is the same as SCHED_FIFO with difference about the scheduling of the threads with the same priority. The threads with the same priority are scheduled in a round-robin mode.

Linux pthread library bug

There's a bug in pthread library regarding setting the schedule policy and priority. See below picture. The right side is the correct one. The setting of the policy and priority MUST come after 'pthread_create()'.

Check the thread policy and priority

With 'chrt' command, the thread schedule policy and priority can be checked from command line.

The threads' id of a process can be found with command: ls /proc/<process id>/task

The first thread in the list is the main thread.

To check a thread's policy and priority:

   chrt -p <thread_id>






2012-03-08

SQLite Performance Test on ARM

Platform: 

ARM9 @400MHz, Linux 2.6.27, Nand Flash, Poco C++ Library for test program

Test Setup:

Test Data Structure:

struct Record {
  UInt64 f0,
  UInt32 f1,
  UInt32 f2,

  UInt32 f3,
  UInt32 f4,
  UInt32 f5,
}

All the test guarantee all the fields have value big enough to use 8 bytes or 4 bytes in the SQLite database.


Database:

Tests are done with 6 different database:

  1. 10K records with index
  2. 10K records without index
  3. 40K records with index
  4. 40K records without index
  5. 100K records with index
  6. 100K records without index
Test Items:

The test focus on testing the time performance on creating these database, random search, random insert, random update, random delete, and bulk insert, as well as the database size.

Test Result:

# ./App 
--------------
DB file: 10k-indexd.db, size: 10000 records, indexed: yes
  Time to create db (in one transaction): 18117681 us, each 1811 us
  DB file size: 784384 bytes
  Random insert 10 times in 254177 us, each 25417 us
  Bulk insert 100 random records in 1031018 us, each 10310 us
  Bulk insert 1000 random records in 3079258 us, each 3079 us
  Random search 10 times in 6534 us, each 653 us
  Random search non exist 10 times in 5169 us, each 516 us
  Random update 10 times in 149859 us, each 14985 us
  Random delete 10 times in 354457 us, each 35445 us
  DB file size: 839680 bytes
  Done!
--------------
DB file: 10k.db, size: 10000 records, indexed: no
  Time to create db (in one transaction): 17041652 us, each 1704 us
  DB file size: 600064 bytes
  Random insert 10 times in 260593 us, each 26059 us
  Bulk insert 100 random records in 614959 us, each 6149 us
  Bulk insert 1000 random records in 1794085 us, each 1794 us
  Random search 10 times in 6615 us, each 661 us
  Random search non exist 10 times in 4967 us, each 496 us
  Random update 10 times in 214609 us, each 21460 us
  Random delete 10 times in 237757 us, each 23775 us
  DB file size: 649216 bytes
  Done!
--------------
DB file: 40k-indexd.db, size: 40000 records, indexed: yes
  Time to create db (in one transaction): 75025477 us, each 1875 us
  DB file size: 3188736 bytes
  Random insert 10 times in 277867 us, each 27786 us
  Bulk insert 100 random records in 1195125 us, each 11951 us
  Bulk insert 1000 random records in 7720179 us, each 7720 us
  Random search 10 times in 7511 us, each 751 us
  Random search non exist 10 times in 5408 us, each 540 us
  Random update 10 times in 301528 us, each 30152 us
  Random delete 10 times in 495317 us, each 49531 us
  DB file size: 3231744 bytes
  Done!
--------------
DB file: 40k.db, size: 40000 records, indexed: no
  Time to create db (in one transaction): 67904547 us, each 1697 us
  DB file size: 2443264 bytes
  Random insert 10 times in 238309 us, each 23830 us
  Bulk insert 100 random records in 645656 us, each 6456 us
  Bulk insert 1000 random records in 4397617 us, each 4397 us
  Random search 10 times in 6991 us, each 699 us
  Random search non exist 10 times in 5942 us, each 594 us
  Random update 10 times in 264723 us, each 26472 us
  Random delete 10 times in 322832 us, each 32283 us
  DB file size: 2486272 bytes
  Done!
--------------
DB file: 100k-indexd.db, size: 100000 records, indexed: yes
  Time to create db (in one transaction): 188842785 us, each 1888 us
  DB file size: 8145920 bytes
  Random insert 10 times in 272809 us, each 27280 us
  Bulk insert 100 random records in 1191890 us, each 11918 us
  Bulk insert 1000 random records in 11077409 us, each 11077 us
  Random search 10 times in 8111 us, each 811 us
  Random search non exist 10 times in 6592 us, each 659 us
  Random update 10 times in 266008 us, each 26600 us
  Random delete 10 times in 444581 us, each 44458 us
  DB file size: 8189952 bytes
  Done!
--------------
DB file: 100k.db, size: 100000 records, indexed: no
  Time to create db (in one transaction): 170344875 us, each 1703 us
  DB file size: 6214656 bytes
  Random insert 10 times in 173419 us, each 17341 us
  Bulk insert 100 random records in 643810 us, each 6438 us
  Bulk insert 1000 random records in 5662638 us, each 5662 us
  Random search 10 times in 14928 us, each 1492 us
  Random search non exist 10 times in 5742 us, each 574 us
  Random update 10 times in 337846 us, each 33784 us
  Random delete 10 times in 291406 us, each 29140 us
  DB file size: 6258688 bytes
  Done!


Result on x86:

As a reference, this is the result on the Ubuntu in VMware virtual machine. (During the test, some other programs also running).


one@ubuntu:~/tmp/app/bin/Linux/i686$ ./App
--------------
DB file: 10k-indexd.db, size: 10000 records, indexed: yes
  Time to create db (in one transaction): 442684 us, each 44 us
  DB file size: 784384 bytes
  Random insert 10 times in 45290 us, each 4529 us
  Bulk insert 100 random records in 23660 us, each 236 us
  Bulk insert 1000 random records in 35567 us, each 35 us
  Random search 10 times in 235 us, each 23 us
  Random search non exist 10 times in 183 us, each 18 us
  Random update 10 times in 37714 us, each 3771 us
  Random delete 10 times in 32167 us, each 3216 us
  DB file size: 847872 bytes
  Done!
--------------
DB file: 10k.db, size: 10000 records, indexed: no
  Time to create db (in one transaction): 414712 us, each 41 us
  DB file size: 600064 bytes
  Random insert 10 times in 41590 us, each 4159 us
  Bulk insert 100 random records in 27343 us, each 273 us
  Bulk insert 1000 random records in 25190 us, each 25 us
  Random search 10 times in 235 us, each 23 us
  Random search non exist 10 times in 201 us, each 20 us
  Random update 10 times in 33079 us, each 3307 us
  Random delete 10 times in 35359 us, each 3535 us
  DB file size: 644096 bytes
  Done!
--------------
DB file: 40k-indexd.db, size: 40000 records, indexed: yes
  Time to create db (in one transaction): 1782219 us, each 44 us
  DB file size: 3188736 bytes
  Random insert 10 times in 26208 us, each 2620 us
  Bulk insert 100 random records in 30767 us, each 307 us
  Bulk insert 1000 random records in 68072 us, each 68 us
  Random search 10 times in 312 us, each 31 us
  Random search non exist 10 times in 267 us, each 26 us
  Random update 10 times in 26809 us, each 2680 us
  Random delete 10 times in 33644 us, each 3364 us
  DB file size: 3231744 bytes
  Done!
--------------
DB file: 40k.db, size: 40000 records, indexed: no
  Time to create db (in one transaction): 1606878 us, each 40 us
  DB file size: 2443264 bytes
  Random insert 10 times in 41207 us, each 4120 us
  Bulk insert 100 random records in 27617 us, each 276 us
  Bulk insert 1000 random records in 200214 us, each 200 us
  Random search 10 times in 308 us, each 30 us
  Random search non exist 10 times in 344 us, each 34 us
  Random update 10 times in 27387 us, each 2738 us
  Random delete 10 times in 40206 us, each 4020 us
  DB file size: 2486272 bytes
  Done!
--------------
DB file: 100k-indexd.db, size: 100000 records, indexed: yes
  Time to create db (in one transaction): 4379620 us, each 43 us
  DB file size: 8145920 bytes
  Random insert 10 times in 29942 us, each 2994 us
  Bulk insert 100 random records in 28524 us, each 285 us
  Bulk insert 1000 random records in 446084 us, each 446 us
  Random search 10 times in 336 us, each 33 us
  Random search non exist 10 times in 284 us, each 28 us
  Random update 10 times in 23732 us, each 2373 us
  Random delete 10 times in 29532 us, each 2953 us
  DB file size: 8189952 bytes
  Done!
--------------
DB file: 100k.db, size: 100000 records, indexed: no
  Time to create db (in one transaction): 3994606 us, each 39 us
  DB file size: 6214656 bytes
  Random insert 10 times in 34722 us, each 3472 us
  Bulk insert 100 random records in 170171 us, each 1701 us
  Bulk insert 1000 random records in 234747 us, each 234 us
  Random search 10 times in 331 us, each 33 us
  Random search non exist 10 times in 280 us, each 28 us
  Random update 10 times in 28106 us, each 2810 us
  Random delete 10 times in 23698 us, each 2369 us
  DB file size: 6258688 bytes
  Done!


Performance of C++ STL map:

As another reference, this is the test result with C++ STL map container on ARM. A map with 100000 cardholders is created as the database, and try to insert 10 new records and search them.


Test map performance: 100000 cardholders
  Random insert 10 cards in 339 us, each 33 us
  Random search 10 cards in 75 us, each 7 us