├── .gitignore ├── CMakeLists.txt ├── README.md ├── Toolchain-raspberrypi-arm.cmake ├── cmake-tddc.sublime-project ├── include ├── LedDriver │ └── LedDriver.h ├── RuntimeError.h └── TutorialConfig.h.in ├── mocks ├── CMakeLists.txt ├── RuntimeErrorStub.c └── RuntimeErrorStub.h ├── src ├── CMakeLists.txt ├── LedDriver │ └── LedDriver.c └── Tutorial │ └── tutorial.cxx └── tests ├── CMakeLists.txt ├── LedDriver └── LedDriverTest.cpp └── RunAllTests.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | *.sublime-workspace -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.7) 2 | project (Tutorial) 3 | 4 | # Check for CppUTest and bail out if they don't have it 5 | if(DEFINED ENV{CPPUTEST_HOME}) 6 | message("Using CppUTest found in $ENV{CPPUTEST_HOME}") 7 | else() 8 | message("CPPUTEST_HOME is not set; You must tell CMake where to find CppUTest") 9 | return() 10 | endif() 11 | 12 | enable_language(C) 13 | enable_language(CXX) 14 | 15 | # The version number 16 | set (Tutorial_VERSION_MAJOR 1) 17 | set (Tutorial_VERSION_MINOR 0) 18 | 19 | set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) 20 | set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib) 21 | set (CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib) 22 | 23 | add_subdirectory(src) 24 | add_subdirectory(mocks) 25 | add_subdirectory(tests) 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cmake-cpputest 2 | ============== 3 | 4 | Skeleton project for development in embedded C using the approach outlined in [Test Driven Development for Emedded C](http://pragprog.com/book/jgade/test-driven-development-for-embedded-c) with testing done via [CppUTest](http://cpputest.org/) and build done using [CMake](http://cmake.org/) 5 | 6 | The demo source code used here (e.g. `LedDriver` example) was taken from the code from the book [Test Driven Development for Emedded C](http://pragprog.com/book/jgade/test-driven-development-for-embedded-c). There isn't very much code, but I just used it as a simple demonstration of a cross-platform build environment that promotes tdd in embedded c. 7 | 8 | **Goals** 9 | * Support dual targeting local dev machine + raspberry pi embedded target...*CHECK* 10 | * Support cross-compiler builds...*CHECK* 11 | * Support build platform independence...*works on Mac OS X and Linux (see below). have not tested Windows* 12 | * Provide a _good_ environment for getting started on a new project...*SUBJECTIVE CHECK* 13 | * Out Of Source Build...*CHECK* 14 | 15 | Why? I'm going to build a [Raspberry Pi](http://raspberrypi.org) project, and compiling on the target is painful for a lot of reasons. I'd prefer to develop, build, & test on a development machine, but still have the capability of building and running tests on the target. This setup ought to work similarly for any target - not just the Raspberry Pi. You can use it and modify it to suit your needs. 16 | 17 | I typically use Mac OS or Linux (Ubuntu) to develop with. Technically this should port to Windows with something like Cygwin, but I haven't bothered to try. 18 | 19 | ## pre-requisites 20 | _NOTE_ I'm using Mac OSX and [homebrew](http://mxcl.github.com/homebrew/) which makes installation a bit simpler. I also use [Sublime Text 2](http://www.sublimetext.com/2) as my editor along with the [SublimeClang plugin](https://github.com/quarnster/SublimeClang). I've already configured the `cmake-tddc.sublime-project` file so stuff "should just work" in the editor (including intellisense and auto-compilation on save). 21 | 22 | Assume the basic GNU toolchain (i.e. make, gcc, g++, etc.) 23 | 24 | ### Install [CMake](http://cmake.org) 25 | 26 | With homebrew (Mac): `brew install cmake` 27 | With aptitude (Ubuntu): `sudo apt-get cmake` 28 | 29 | Useful links: 30 | * [CMake Manual](http://cmake.org/cmake/help/v2.8.9/cmake.html) 31 | * [CMake Wiki](http://www.itk.org/Wiki/CMake) 32 | * [CMake Mailing List](http://cmake.3232098.n2.nabble.com/) 33 | 34 | ### Install [CppUTest](http://cpputest.org) 35 | 36 | With git: `git clone git://github.com/cpputest/cpputest.git && cd cpputest && make` 37 | With homebrew: `brew install cpputest` 38 | 39 | Now, create an environment variable that points to it. Add the following line to `.bash_profile` or similar: `export CPPUTEST_HOME=/path/to/cpputest` 40 | 41 | ### Build 42 | ```sh 43 | $ mkdir build && cd build 44 | $ cmake .. 45 | $ make 46 | ``` 47 | 48 | ### Run Tests 49 | ```sh 50 | ~/git/cmake-cpputest$ cd build && ./bin/RunAllTests 51 | 52 | ......!......... 53 | OK (16 tests, 15 ran, 20 checks, 1 ignored, 0 filtered out, 0 ms) 54 | ``` 55 | 56 | These 16 tests are for `LedDriver` which is sample code from the book. There's even an example of using a stub via the linker (`RuntimeErrorStub.c`). 57 | 58 | ### Cross-Compile for Raspberry Pi 59 | I gave up trying to do this on the Mac. It might be possible, but I couldn't find anything decent on how to setup Gnu compiler toolchain for the Arm/Pi hardware like there is for Linux. So, the path I'm taking is to use Linux to cross-compile when I need to, but I can still develop and test on the Mac using tdd/mocks. Only if I want to cross-compile for the Pi and push the binaries will I need to switch. 60 | 61 | So, this step is for Ubuntu only; I grab the pre-built toolchain for Pi: 62 | 63 | ``` 64 | $ mkdir -p ~/git/raspberrypi/ && cd ~/git/raspberrypi 65 | $ git clone git://github.com/raspberrypi/tools.git 66 | ``` 67 | 68 | Now, create an environment variable that points to the tools. Add the following line to `.bashrc` or similar: `export PI_TOOLS_HOME=~/git/raspberrypi/tools` 69 | 70 | _this is my path, yours may be different_ 71 | 72 | Now, edit the file `Toolchain-raspberrypi-arm.cmake` and make sure the paths are consistent with `$PI_TOOLS_HOME`. This is the [cross-compiler configuration for CMake](http://www.cmake.org/Wiki/CMake_Cross_Compiling#The_toolchain_file). 73 | 74 | In order to build, you'll also need to [cross-compile a version of CppUTest](#cross-compile-cpputest-for-embedded-target), and you'll have to override the environment variable `$CPPUTEST_HOME` with the Arm version of the library. 75 | 76 | Build as follows (create a different build directory for the Arm stuff so we don't intermix them) 77 | ```sh 78 | $ mkdir pibuild && cd pibuild 79 | $ cmake -DCMAKE_TOOLCHAIN_FILE=../Toolchain-raspberrypi-arm.cmake .. 80 | ``` 81 | 82 | With fingers crossed, it will build ok. 83 | 84 | #### Cross-Compile CppUTest for embedded target 85 | Actually, instead of cross-compiling the CppUTest library, I chose to build it with the QEMU emulator. 86 | It seemed easier than messing with the cross-compiler. This is a one-time build, since the code for CppUTest will not change often. I wanted to setup the `Emulator` anyway with QEMU, so it kills two birds with one stone. 87 | 88 | You might ask why not just build the whole source - including CppUTest in QEMU or on the Pi itself, and the reason is that it is slow. It took about 5 minutes to compile CppUTest. I want a dev. environment that is fast and allows me to mock the hardware and dependencies. 89 | 90 | Install [QEMU](http://wiki.qemu.org/Main_Page) 91 | ```sh 92 | $ sudo apt-get install qemu qemu-system 93 | ``` 94 | 95 | Download the [Raspbian Wheezy Image](http://www.raspberrypi.org/downloads) 96 | ```sh 97 | $ wget http://files.velocix.com/c1410/images/raspbian/2012-08-16-wheezy-raspbian/2012-08-16-wheezy-raspbian.zip 98 | $ unzip ./2012-08-16-wheezy-raspbian.zip 99 | ``` 100 | 101 | Download the QEMU Kernel Image 102 | ```sh 103 | $ wget http://xecdesign.com/downloads/linux-qemu/kernel-qemu 104 | ``` 105 | 106 | Launch the image. Consult [QEMU docs](http://wiki.qemu.org/Manual) or [online tutorials](http://xecdesign.com/qemu-emulating-raspberry-pi-the-easy-way/) for tweaks. I had to use the `-cpu arm1136-r2` target. 107 | 108 | ```sh 109 | $ qemu-system-arm -kernel ./kernel-qemu -cpu arm1136-r2 -m 256 -M versatilepb -no-reboot -serial stdio -append "root=/dev/sda2 panic=1" -hda ./2012-08-16-wheezy-raspbian.img 110 | ``` 111 | 112 | Now, in the emulator, grab the source for CppUTest from git and build it 113 | 114 | ```sh 115 | $ mkdir ~/git && cd ~/git 116 | $ git clone git://github.com/cpputest/cpputest && cd cpputest 117 | $ make 118 | ``` 119 | 120 | Now, kill the emulator, mount the filesystem and copy the library to the host. Run `fdisk` to figure out the start sector. 121 | 122 | ```sh 123 | $ mkdir -p /mnt/pi 124 | $ fdisk -lu ./2012-08-16-wheezy-raspbian.img 125 | 126 | Disk ./2012-08-16-wheezy-raspbian.img: 1939 MB, 1939865600 bytes 127 | 255 heads, 63 sectors/track, 235 cylinders, total 3788800 sectors 128 | Units = sectors of 1 * 512 = 512 bytes 129 | Sector size (logical/physical): 512 bytes / 512 bytes 130 | I/O size (minimum/optimal): 512 bytes / 512 bytes 131 | Disk identifier: 0x000108cb 132 | 133 | Device Boot Start End Blocks Id System 134 | ./2012-08-16-wheezy-raspbian.img1 8192 122879 57344 c W95 FAT32 (LBA) 135 | ./2012-08-16-wheezy-raspbian.img2 122880 3788799 1832960 83 Linux 136 | 137 | $ sudo mount -t ext4 -o loop,offset=$((512*122880)) ./2012-08-16-wheezy-raspbian.img /mnt/pi 138 | ``` 139 | 140 | Now, create a directory on the host to hold the Arm library and include files. 141 | 142 | ```sh 143 | $ mkdir ~/cpputest-arm 144 | # Assume you git cloned the ccputest source to ~/git/cpputest 145 | $ cp -R ~/git/cpputest/include ~/cpputest-arm 146 | $ mkdir ~/cpputest-arm/lib 147 | $ cp /mnt/pi/home/pi/git/cpputest/lib/libCppUTest.a ~/cpputest-arm/lib 148 | $ sudo umount /mnt/pi 149 | ``` 150 | 151 | Now, if you want to build for the Pi target, including the test binary, you update `CPPUTEST_HOME` and use the CMake toolchain file for the Pi. 152 | 153 | ```sh 154 | $ export CPPUTEST_HOME=~/cpputest-arm 155 | $ cd ~/git/cmake-cpputest/pibuild 156 | $ cmake -DCMAKE_TOOLCHAIN_FILE=../Toolchain-raspberrypi-arm.cmake .. 157 | $ make 158 | ``` 159 | 160 | Outputs in `bin` and `lib` directories. You can re-mount the Pi filesystem, copy them over, restart emulator and validate that they work ok (they do for me). 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /Toolchain-raspberrypi-arm.cmake: -------------------------------------------------------------------------------- 1 | # this one is important 2 | SET (CMAKE_SYSTEM_NAME Linux) 3 | # this one not so much 4 | SET (CMAKE_SYSTEM_VERSION 1) 5 | 6 | # Check for Raspberry Pi Tools and bail out if they don't have it 7 | if(DEFINED ENV{PI_TOOLS_HOME}) 8 | message("Using Raspberry Pi Tools found in $ENV{PI_TOOLS_HOME}") 9 | else() 10 | message("PI_TOOLS_HOME is not set; You must tell CMake where to find Raspberry Pi Tools (cross-compiler)") 11 | return() 12 | endif() 13 | 14 | SET (PiToolsDir arm-bcm2708/arm-bcm2708hardfp-linux-gnueabi) 15 | 16 | # specify the cross compiler 17 | SET (CMAKE_C_COMPILER $ENV{PI_TOOLS_HOME}/${PiToolsDir}/bin/arm-bcm2708hardfp-linux-gnueabi-gcc) 18 | SET (CMAKE_CXX_COMPILER $ENV{PI_TOOLS_HOME}/${PiToolsDir}/bin/arm-bcm2708hardfp-linux-gnueabi-g++) 19 | 20 | # where is the target environment 21 | SET (CMAKE_FIND_ROOT_PATH $ENV{PI_TOOLS_HOME}/${PiToolsDir}) 22 | 23 | # search for programs in the build host directories 24 | SET (CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 25 | 26 | # for libraries and headers in the target directories 27 | SET (CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 28 | SET (CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) -------------------------------------------------------------------------------- /cmake-tddc.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "build_systems": 3 | [ 4 | { 5 | "cmd": 6 | [ 7 | "make" 8 | ], 9 | "name": "Make" 10 | } 11 | ], 12 | "folders": 13 | [ 14 | { 15 | "path": "/Users/davis/git/cmake-cpputest" 16 | }, 17 | { 18 | "path": "/Users/davis/Documents/tdd-embedded-c" 19 | } 20 | ], 21 | "settings": 22 | { 23 | "sublimeclang_options": 24 | [ 25 | "-I${folder:${project_path:cmake-tddc.sublime-project}}/include/**", 26 | "-I${folder:${project_path:cmake-tddc.sublime-project}}/include", 27 | "-I${CPPUTEST_HOME}/include/**", 28 | "-I${folder:${project_path:cmake-tddc.sublime-project}}/mocks" 29 | ], 30 | "tab_size": 2 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /include/LedDriver/LedDriver.h: -------------------------------------------------------------------------------- 1 | /*** 2 | * Excerpted from "Test-Driven Development for Embedded C", 3 | * published by The Pragmatic Bookshelf. 4 | * Copyrights apply to this code. It may not be used to create training material, 5 | * courses, books, articles, and the like. Contact us if you are in doubt. 6 | * We make no guarantees that this code is fit for any purpose. 7 | * Visit http://www.pragmaticprogrammer.com/titles/jgade for more book information. 8 | ***/ 9 | /*- ------------------------------------------------------------------ -*/ 10 | /*- Copyright (c) James W. Grenning -- All Rights Reserved -*/ 11 | /*- For use by owners of Test-Driven Development for Embedded C, -*/ 12 | /*- and attendees of Renaissance Software Consulting, Co. training -*/ 13 | /*- classes. -*/ 14 | /*- -*/ 15 | /*- Available at http://pragprog.com/titles/jgade/ -*/ 16 | /*- ISBN 1-934356-62-X, ISBN13 978-1-934356-62-3 -*/ 17 | /*- -*/ 18 | /*- Authorized users may use this source code in your own -*/ 19 | /*- projects, however the source code may not be used to -*/ 20 | /*- create training material, courses, books, articles, and -*/ 21 | /*- the like. We make no guarantees that this source code is -*/ 22 | /*- fit for any purpose. -*/ 23 | /*- -*/ 24 | /*- www.renaissancesoftware.net james@renaissancesoftware.net -*/ 25 | /*- ------------------------------------------------------------------ -*/ 26 | #ifndef D_LedDriver_H 27 | #define D_LedDriver_H 28 | #include 29 | 30 | #define TRUE 1 31 | #define FALSE 0 32 | typedef int BOOL; 33 | 34 | 35 | void LedDriver_Create(uint16_t * ledsAddress); 36 | void LedDriver_Destroy(void); 37 | 38 | void LedDriver_TurnOn(int ledNumber); 39 | void LedDriver_TurnOff(int ledNumber); 40 | void LedDriver_TurnAllOn(void); 41 | void LedDriver_TurnAllOff(void); 42 | BOOL LedDriver_IsOn(int ledNumber); 43 | BOOL LedDriver_IsOff(int ledNumber); 44 | #endif /* D_LedDriver_H */ -------------------------------------------------------------------------------- /include/RuntimeError.h: -------------------------------------------------------------------------------- 1 | /*** 2 | * Excerpted from "Test-Driven Development for Embedded C", 3 | * published by The Pragmatic Bookshelf. 4 | * Copyrights apply to this code. It may not be used to create training material, 5 | * courses, books, articles, and the like. Contact us if you are in doubt. 6 | * We make no guarantees that this code is fit for any purpose. 7 | * Visit http://www.pragmaticprogrammer.com/titles/jgade for more book information. 8 | ***/ 9 | /*- ------------------------------------------------------------------ -*/ 10 | /*- Copyright (c) James W. Grenning -- All Rights Reserved -*/ 11 | /*- For use by owners of Test-Driven Development for Embedded C, -*/ 12 | /*- and attendees of Renaissance Software Consulting, Co. training -*/ 13 | /*- classes. -*/ 14 | /*- -*/ 15 | /*- Available at http://pragprog.com/titles/jgade/ -*/ 16 | /*- ISBN 1-934356-62-X, ISBN13 978-1-934356-62-3 -*/ 17 | /*- -*/ 18 | /*- Authorized users may use this source code in your own -*/ 19 | /*- projects, however the source code may not be used to -*/ 20 | /*- create training material, courses, books, articles, and -*/ 21 | /*- the like. We make no guarantees that this source code is -*/ 22 | /*- fit for any purpose. -*/ 23 | /*- -*/ 24 | /*- www.renaissancesoftware.net james@renaissancesoftware.net -*/ 25 | /*- ------------------------------------------------------------------ -*/ 26 | 27 | /// 28 | /// At runtime, put an entry in an event log 29 | /// 30 | /// @param message the message you want to log 31 | /// @param parameter the param argument number of the function 32 | /// @param file the file to log to 33 | /// @param line the line number for stack trace 34 | /// 35 | void RunTimeError(const char * message, int parameter, 36 | const char * file, int line); 37 | 38 | /// Code should use this macro instead of the function 39 | #define RUNTIME_ERROR(description, parameter) RuntimeError(description, parameter, __FILE__, __LINE__) -------------------------------------------------------------------------------- /include/TutorialConfig.h.in: -------------------------------------------------------------------------------- 1 | #define Tutorial_VERSION_MAJOR 1 2 | #define Tutorial_VERSION_MINOR 0 3 | -------------------------------------------------------------------------------- /mocks/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project (Tutorial) 2 | 3 | include_directories(${PROJECT_SOURCE_DIR}/../include) 4 | 5 | add_library(RuntimeErrorStub RuntimeErrorStub.c) -------------------------------------------------------------------------------- /mocks/RuntimeErrorStub.c: -------------------------------------------------------------------------------- 1 | /*** 2 | * Excerpted from "Test-Driven Development for Embedded C", 3 | * published by The Pragmatic Bookshelf. 4 | * Copyrights apply to this code. It may not be used to create training material, 5 | * courses, books, articles, and the like. Contact us if you are in doubt. 6 | * We make no guarantees that this code is fit for any purpose. 7 | * Visit http://www.pragmaticprogrammer.com/titles/jgade for more book information. 8 | ***/ 9 | /*- ------------------------------------------------------------------ -*/ 10 | /*- Copyright (c) James W. Grenning -- All Rights Reserved -*/ 11 | /*- For use by owners of Test-Driven Development for Embedded C, -*/ 12 | /*- and attendees of Renaissance Software Consulting, Co. training -*/ 13 | /*- classes. -*/ 14 | /*- -*/ 15 | /*- Available at http://pragprog.com/titles/jgade/ -*/ 16 | /*- ISBN 1-934356-62-X, ISBN13 978-1-934356-62-3 -*/ 17 | /*- -*/ 18 | /*- Authorized users may use this source code in your own -*/ 19 | /*- projects, however the source code may not be used to -*/ 20 | /*- create training material, courses, books, articles, and -*/ 21 | /*- the like. We make no guarantees that this source code is -*/ 22 | /*- fit for any purpose. -*/ 23 | /*- -*/ 24 | /*- www.renaissancesoftware.net james@renaissancesoftware.net -*/ 25 | /*- ------------------------------------------------------------------ -*/ 26 | 27 | // Stub implementation of RuntimeError.h 28 | 29 | #include "RuntimeErrorStub.h" 30 | static const char * message = "No Error"; 31 | static int parameter = -1; 32 | static const char * file = 0; 33 | static int line = -1; 34 | 35 | /// 36 | /// Allows us to reset internal stats before each test 37 | /// 38 | void RuntimeErrorStub_Reset(void) 39 | { 40 | message = "No Error"; 41 | parameter = -1; 42 | } 43 | 44 | /// 45 | /// Get the last error message 46 | /// @return the last error message 47 | /// 48 | const char * RuntimeErrorStub_GetLastError(void) 49 | { 50 | return message; 51 | } 52 | 53 | /// 54 | /// Implementation of RuntimeError.h - pass a message to be stored in an event log 55 | /// @param m the message to log 56 | /// @param p the parameter position in the function 57 | /// @param f pointer to file 58 | /// @param l the line number 59 | void RuntimeError(const char * m, int p, const char * f, int l) 60 | { 61 | message = m; 62 | parameter = p; 63 | file = f; 64 | line = l; 65 | } 66 | 67 | /// 68 | /// Allows tests to get the last parameter position 69 | /// @return the last parameter number 70 | /// 71 | int RuntimeErrorStub_GetLastParameter(void) 72 | { 73 | return parameter; 74 | } -------------------------------------------------------------------------------- /mocks/RuntimeErrorStub.h: -------------------------------------------------------------------------------- 1 | /*** 2 | * Excerpted from "Test-Driven Development for Embedded C", 3 | * published by The Pragmatic Bookshelf. 4 | * Copyrights apply to this code. It may not be used to create training material, 5 | * courses, books, articles, and the like. Contact us if you are in doubt. 6 | * We make no guarantees that this code is fit for any purpose. 7 | * Visit http://www.pragmaticprogrammer.com/titles/jgade for more book information. 8 | ***/ 9 | /*- ------------------------------------------------------------------ -*/ 10 | /*- Copyright (c) James W. Grenning -- All Rights Reserved -*/ 11 | /*- For use by owners of Test-Driven Development for Embedded C, -*/ 12 | /*- and attendees of Renaissance Software Consulting, Co. training -*/ 13 | /*- classes. -*/ 14 | /*- -*/ 15 | /*- Available at http://pragprog.com/titles/jgade/ -*/ 16 | /*- ISBN 1-934356-62-X, ISBN13 978-1-934356-62-3 -*/ 17 | /*- -*/ 18 | /*- Authorized users may use this source code in your own -*/ 19 | /*- projects, however the source code may not be used to -*/ 20 | /*- create training material, courses, books, articles, and -*/ 21 | /*- the like. We make no guarantees that this source code is -*/ 22 | /*- fit for any purpose. -*/ 23 | /*- -*/ 24 | /*- www.renaissancesoftware.net james@renaissancesoftware.net -*/ 25 | /*- ------------------------------------------------------------------ -*/ 26 | 27 | #ifndef D_RuntimeErrorStub_H 28 | #define D_RuntimeErrorStub_H 29 | 30 | #include "RuntimeError.h" 31 | 32 | /// 33 | /// Allows test code to reset the stub 34 | /// 35 | void RuntimeErrorStub_Reset(void); 36 | 37 | /// 38 | /// Retrieve the last error message passed to the stub 39 | /// @return the last error message passed to the stub 40 | /// 41 | const char * RuntimeErrorStub_GetLastError(void); 42 | 43 | /// 44 | /// Retrieve the position of the last parameter that caused the error 45 | /// @return the position of the last parameter that caused the error 46 | /// 47 | int RuntimeErrorStub_GetLastParameter(void); 48 | 49 | #endif -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project (Tutorial) 2 | 3 | include_directories(${PROJECT_SOURCE_DIR}/../include) 4 | include_directories(${PROJECT_SOURCE_DIR}/../include/LedDriver/) 5 | 6 | # Configure a header file to pass some of the CMake settings 7 | # to the source code 8 | configure_file ( 9 | "${PROJECT_SOURCE_DIR}/../include/TutorialConfig.h.in" 10 | "${PROJECT_BINARY_DIR}/TutorialConfig.h" 11 | ) 12 | 13 | add_executable(Tutorial ./Tutorial/tutorial.cxx) 14 | add_library(LedDriver ./LedDriver/LedDriver.c) -------------------------------------------------------------------------------- /src/LedDriver/LedDriver.c: -------------------------------------------------------------------------------- 1 | /*** 2 | * Excerpted from "Test-Driven Development for Embedded C", 3 | * published by The Pragmatic Bookshelf. 4 | * Copyrights apply to this code. It may not be used to create training material, 5 | * courses, books, articles, and the like. Contact us if you are in doubt. 6 | * We make no guarantees that this code is fit for any purpose. 7 | * Visit http://www.pragmaticprogrammer.com/titles/jgade for more book information. 8 | ***/ 9 | /*- ------------------------------------------------------------------ -*/ 10 | /*- Copyright (c) James W. Grenning -- All Rights Reserved -*/ 11 | /*- For use by owners of Test-Driven Development for Embedded C, -*/ 12 | /*- and attendees of Renaissance Software Consulting, Co. training -*/ 13 | /*- classes. -*/ 14 | /*- -*/ 15 | /*- Available at http://pragprog.com/titles/jgade/ -*/ 16 | /*- ISBN 1-934356-62-X, ISBN13 978-1-934356-62-3 -*/ 17 | /*- -*/ 18 | /*- Authorized users may use this source code in your own -*/ 19 | /*- projects, however the source code may not be used to -*/ 20 | /*- create training material, courses, books, articles, and -*/ 21 | /*- the like. We make no guarantees that this source code is -*/ 22 | /*- fit for any purpose. -*/ 23 | /*- -*/ 24 | /*- www.renaissancesoftware.net james@renaissancesoftware.net -*/ 25 | /*- ------------------------------------------------------------------ -*/ 26 | 27 | #include "LedDriver.h" 28 | #include "RuntimeError.h" 29 | 30 | static uint16_t * ledsAddress; 31 | static uint16_t ledsImage; 32 | 33 | enum { 34 | ALL_LEDS_ON = ~0, 35 | ALL_LEDS_OFF = 0 36 | }; 37 | 38 | /// 39 | /// Initialize Led Driver with the hardware address 40 | /// @param address the hardware address for the LEDs 41 | /// 42 | void LedDriver_Create(uint16_t * address) 43 | { 44 | ledsAddress = address; 45 | ledsImage = ALL_LEDS_OFF; 46 | *ledsAddress = ledsImage; 47 | } 48 | 49 | /// 50 | /// Destroy the driver 51 | /// 52 | void LedDriver_Destroy(void) { } 53 | 54 | 55 | enum { 56 | FIRST_LED = 1, 57 | LAST_LED = 16 58 | }; 59 | 60 | /// 61 | /// Test if an LED number is out of bounds 62 | /// @param ledNumber the number to test for 63 | /// @return TRUE if out of bounds 64 | static BOOL IsLedOutOfBounds(int ledNumber) 65 | { 66 | if ((ledNumber < FIRST_LED) || (ledNumber > LAST_LED)) 67 | { 68 | RUNTIME_ERROR("LED Driver: out-of-bounds LED", ledNumber); 69 | return TRUE; 70 | } 71 | return FALSE; 72 | } 73 | 74 | /// 75 | /// Write to the LED memory 76 | /// 77 | static void updateHardware(void) 78 | { 79 | *ledsAddress = ledsImage; 80 | } 81 | 82 | /// 83 | /// Helper function to convert led number to bit 84 | static uint16_t convertLedNumberToBit(int number) 85 | { 86 | return 1 << (number - 1); 87 | } 88 | 89 | /// 90 | /// Toggles the bit of the led number on 91 | /// @param the led number to toggle 92 | static void setLedImageBit(int ledNumber) 93 | { 94 | ledsImage |= convertLedNumberToBit(ledNumber); 95 | } 96 | 97 | /// 98 | /// Toggles the bit of the led number off 99 | /// @param the led number to toggle 100 | static void clearLedImageBit(int ledNumber) 101 | { 102 | ledsImage &= ~convertLedNumberToBit(ledNumber); 103 | } 104 | 105 | /// 106 | /// Turn an individual LED on 107 | /// @param ledNumber the LED number 108 | /// 109 | void LedDriver_TurnOn(int ledNumber) 110 | { 111 | if (IsLedOutOfBounds(ledNumber)) 112 | return; 113 | 114 | setLedImageBit(ledNumber); 115 | updateHardware(); 116 | } 117 | 118 | /// 119 | /// Turn an individual LED off 120 | /// @param ledNumber the LED number 121 | void LedDriver_TurnOff(int ledNumber) 122 | { 123 | if (IsLedOutOfBounds(ledNumber)) 124 | return; 125 | 126 | clearLedImageBit(ledNumber); 127 | updateHardware(); 128 | } 129 | 130 | /// 131 | /// Turn all the LEDs on 132 | void LedDriver_TurnAllOn(void) 133 | { 134 | ledsImage = ALL_LEDS_ON; 135 | updateHardware(); 136 | } 137 | 138 | /// 139 | /// Turn all the LEDs off 140 | void LedDriver_TurnAllOff(void) 141 | { 142 | ledsImage = ALL_LEDS_OFF; 143 | updateHardware(); 144 | } 145 | 146 | /// 147 | /// Returns true if the LED for ledNumber is on 148 | BOOL LedDriver_IsOn(int ledNumber) 149 | { 150 | if (IsLedOutOfBounds(ledNumber)) 151 | return FALSE; 152 | return 0 != (ledsImage & convertLedNumberToBit(ledNumber)); 153 | } 154 | 155 | // 156 | // Returns true if the LED for ledNumber is off 157 | BOOL LedDriver_IsOff(int ledNumber) 158 | { 159 | return !LedDriver_IsOn(ledNumber); 160 | } 161 | -------------------------------------------------------------------------------- /src/Tutorial/tutorial.cxx: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main (int argc, char *argv[]) { 6 | if (argc < 2) { 7 | fprintf(stdout, "Usage: %s number\n", argv[0]); 8 | return 1; 9 | } 10 | 11 | double inputValue = atof(argv[1]); 12 | double outputValue = sqrt(inputValue); 13 | fprintf(stdout, "The square root of %g is %g\n", 14 | inputValue, outputValue ); 15 | return 0; 16 | } -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project (Tutorial) 2 | 3 | # include project source 4 | include_directories(${PROJECT_SOURCE_DIR}/../include) 5 | include_directories(${PROJECT_SOURCE_DIR}/../include/LedDriver) 6 | 7 | # include mocks only for test code 8 | include_directories(${PROJECT_SOURCE_DIR}/../mocks) 9 | 10 | # include CppUTest headers 11 | include_directories($ENV{CPPUTEST_HOME}/include) 12 | 13 | # add cpputest library 14 | add_library(imp_cpputest STATIC IMPORTED) 15 | set_property(TARGET imp_cpputest PROPERTY 16 | IMPORTED_LOCATION $ENV{CPPUTEST_HOME}/src/CppUTest/libCppUTest.a) 17 | 18 | # build test library for LedDriver 19 | add_library(LedDriverTest ./LedDriver/LedDriverTest.cpp) 20 | 21 | add_executable(RunAllTests RunAllTests.cpp) 22 | target_link_libraries(RunAllTests imp_cpputest LedDriverTest LedDriver RuntimeErrorStub) 23 | -------------------------------------------------------------------------------- /tests/LedDriver/LedDriverTest.cpp: -------------------------------------------------------------------------------- 1 | /*** 2 | * Excerpted from "Test-Driven Development for Embedded C", 3 | * published by The Pragmatic Bookshelf. 4 | * Copyrights apply to this code. It may not be used to create training material, 5 | * courses, books, articles, and the like. Contact us if you are in doubt. 6 | * We make no guarantees that this code is fit for any purpose. 7 | * Visit http://www.pragmaticprogrammer.com/titles/jgade for more book information. 8 | ***/ 9 | /*- ------------------------------------------------------------------ -*/ 10 | /*- Copyright (c) James W. Grenning -- All Rights Reserved -*/ 11 | /*- For use by owners of Test-Driven Development for Embedded C, -*/ 12 | /*- and attendees of Renaissance Software Consulting, Co. training -*/ 13 | /*- classes. -*/ 14 | /*- -*/ 15 | /*- Available at http://pragprog.com/titles/jgade/ -*/ 16 | /*- ISBN 1-934356-62-X, ISBN13 978-1-934356-62-3 -*/ 17 | /*- -*/ 18 | /*- Authorized users may use this source code in your own -*/ 19 | /*- projects, however the source code may not be used to -*/ 20 | /*- create training material, courses, books, articles, and -*/ 21 | /*- the like. We make no guarantees that this source code is -*/ 22 | /*- fit for any purpose. -*/ 23 | /*- -*/ 24 | /*- www.renaissancesoftware.net james@renaissancesoftware.net -*/ 25 | /*- ------------------------------------------------------------------ -*/ 26 | 27 | #include "CppUTest/TestHarness.h" 28 | 29 | extern "C" 30 | { 31 | #include "LedDriver.h" 32 | #include "RuntimeErrorStub.h" 33 | } 34 | 35 | TEST_GROUP(LedDriver) 36 | { 37 | 38 | uint16_t virtualLeds; 39 | 40 | void setup() 41 | { 42 | virtualLeds = 0; 43 | LedDriver_Create(&virtualLeds); 44 | } 45 | 46 | void teardown() 47 | { 48 | LedDriver_Destroy(); 49 | } 50 | }; 51 | 52 | TEST(LedDriver, LedsAreOffAfterCreate) 53 | { 54 | virtualLeds = 0xffff; 55 | LedDriver_Create(&virtualLeds); 56 | LONGS_EQUAL(0, virtualLeds); 57 | } 58 | 59 | TEST(LedDriver, TurnOnLedOne) 60 | { 61 | LedDriver_TurnOn(1); 62 | LONGS_EQUAL(1, virtualLeds); 63 | } 64 | 65 | TEST(LedDriver, TurnOffLedOne) 66 | { 67 | LedDriver_TurnOn(1); 68 | LedDriver_TurnOff(1); 69 | LONGS_EQUAL(0, virtualLeds); 70 | } 71 | 72 | TEST(LedDriver, TurnOnMultipleLeds) 73 | { 74 | LedDriver_TurnOn(9); 75 | LedDriver_TurnOn(8); 76 | LONGS_EQUAL(0x180, virtualLeds); 77 | } 78 | 79 | TEST(LedDriver, TurnOffAnyLed) 80 | { 81 | LedDriver_TurnAllOn(); 82 | LedDriver_TurnOff(8); 83 | LONGS_EQUAL(0xff7f, virtualLeds); 84 | } 85 | 86 | TEST(LedDriver, LedMemoryIsNotReadable) 87 | { 88 | virtualLeds = 0xffff; 89 | LedDriver_TurnOn(8); 90 | LONGS_EQUAL(0x80, virtualLeds); 91 | } 92 | 93 | TEST(LedDriver, UpperAndLowerBounds) 94 | { 95 | LedDriver_TurnOn(1); 96 | LedDriver_TurnOn(16); 97 | LONGS_EQUAL(0x8001, virtualLeds); 98 | } 99 | 100 | TEST(LedDriver, OutOfBoundsTurnOnDoesNoHarm) 101 | { 102 | LedDriver_TurnOn(-1); 103 | LedDriver_TurnOn(0); 104 | LedDriver_TurnOn(17); 105 | LedDriver_TurnOn(3141); 106 | LONGS_EQUAL(0, virtualLeds); 107 | } 108 | 109 | TEST(LedDriver, OutOfBoundsTurnOffDoesNoHarm) 110 | { 111 | LedDriver_TurnAllOn(); 112 | 113 | LedDriver_TurnOff(-1); 114 | LedDriver_TurnOff(0); 115 | LedDriver_TurnOff(17); 116 | LedDriver_TurnOff(3141); 117 | LONGS_EQUAL(0xffff, virtualLeds); 118 | } 119 | 120 | IGNORE_TEST(LedDriver, OutOfBoundsToDo) 121 | { 122 | // demo shows how to IGNORE a test 123 | } 124 | 125 | TEST(LedDriver, OutOfBoundsProducesRuntimeError) 126 | { 127 | LedDriver_TurnOn(-1); 128 | STRCMP_EQUAL("LED Driver: out-of-bounds LED", RuntimeErrorStub_GetLastError()); 129 | } 130 | 131 | TEST(LedDriver, IsOn) 132 | { 133 | CHECK_EQUAL(FALSE, LedDriver_IsOn(1)); 134 | LedDriver_TurnOn(1); 135 | CHECK_EQUAL(TRUE, LedDriver_IsOn(1)); 136 | } 137 | 138 | TEST(LedDriver, IsOff) 139 | { 140 | CHECK_EQUAL(TRUE, LedDriver_IsOff(12)); 141 | LedDriver_TurnOn(12); 142 | CHECK_EQUAL(FALSE, LedDriver_IsOff(12)); 143 | } 144 | 145 | TEST(LedDriver, OutOfBoundsLedsAreAlwaysOff) 146 | { 147 | CHECK_EQUAL(TRUE, LedDriver_IsOff(0)); 148 | CHECK_EQUAL(TRUE, LedDriver_IsOff(17)); 149 | CHECK_EQUAL(FALSE, LedDriver_IsOn(0)); 150 | CHECK_EQUAL(FALSE, LedDriver_IsOn(17)); 151 | } 152 | 153 | TEST(LedDriver, AllOn) 154 | { 155 | LedDriver_TurnAllOn(); 156 | LONGS_EQUAL(0xffff, virtualLeds); 157 | } 158 | 159 | TEST(LedDriver, AllOff) 160 | { 161 | LedDriver_TurnAllOn(); 162 | LedDriver_TurnAllOff(); 163 | LONGS_EQUAL(0, virtualLeds); 164 | } 165 | 166 | -------------------------------------------------------------------------------- /tests/RunAllTests.cpp: -------------------------------------------------------------------------------- 1 | #include "CppUTest/CommandLineTestRunner.h" 2 | 3 | IMPORT_TEST_GROUP(LedDriver); 4 | 5 | int main(int argc, char** argv) 6 | { 7 | return RUN_ALL_TESTS(argc, argv); 8 | } --------------------------------------------------------------------------------