Mocking file descriptor system calls

2,587 views
Skip to first unread message

billh...@ecologix.ca

unread,
Jun 30, 2014, 9:13:19 PM6/30/14
to cppu...@googlegroups.com
Hello!

New to cpputest here. I am attempting to write a simple I2C driver for the Beaglebone black, and I hope to use CppUTest in this project to ensure my device-specific drivers do not fall victim to bugs in my underlying I2C implementation.

My question is, does anyone have any hints for me to mock out linux system calls? This driver will require ioctl(), open(), read(), and write(). Ideally I'd like to mock these out, as I am cross-compiling on my dev system (does not have the I2C bus file handle), for a system that has the I2C file handle. It looks like I would have to use the mock_c() functions, but there isn't very much documentation on how to use them. Additionally ioctl() has an unsigned long type, which means I would have to write a custom comparator as far as I understand.

Alternatively, I could also mock out the functions in <linux/i2c_dev.h> which allow for more complex sequences of reads and writes, but I figured it would be best to start with the simple.

A sample i2c transaction would be as follows, to give you guys a better idea of the types of things I'd have to unit test:

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>

// Open the i2c bus file descriptor
int g_i2cFile;
if((g_i2cFile = open(“dev/i2c-3″, O_RDWR)) < 0){
// ERROR HANDLING
perror(“Failed to open the i2c bus”);
return -1;
}
// Set the device address on the bus
int address = 0×19;
if (ioctl(g_i2cFile, I2C_SLAVE, address) < 0) {
perror(“i2cSetAddress”);
exit(1);
}

Example of a write:

// Write data to the device
unsigned char I2C_WR_Buf[MAX_BUFFER_SIZE];
I2C_WR_Buf[0] = Reg_ADDR;
I2C_WR_Buf[1] = Data;
if(write(g_i2cFile, I2C_WR_Buf,2) != 2) {
perror(“Write Error”);
}
// Close the handle
close(g_i2cFile);

Example of a read:

unsigned char I2C_WR_Buf[MAX_BUFFER_SIZE];
unsigned char I2C_RD_Buf[MAX_BUFFER_SIZE];
I2C_WR_Buf[0] = Reg_ADDR;
i2cSetAddress(DEVICE_ADDR);
if(write(g_i2cFile, I2C_WR_Buf, 1) != 1) {
perror(“Write Error”);
}
i2cSetAddress(DEVICE_ADDR); 
if(read(g_i2cFile, I2C_RD_Buf, n) !=n){
perror(“Read Error”);
}
// Close the handle
close(g_i2cFile);

Thanks for any help you can provide me!

James Grenning

unread,
Jun 30, 2014, 11:52:07 PM6/30/14
to cppu...@googlegroups.com
Hi Bill

I'll take a stab at a first test. I find it be preferable if you do all
the mocking from C++ when I use mocks. I sometimes hand craft test
doubles.

Given the example code, you can start with this test:

TEST(IC2_DRIVER, open_fails)
{
mock("os")
.expectOneCall("open")
.withParameter("path", "dev/i2c-3")
.withParameter("mode", O_RDWR)
.andReturnValue(-1);
mock("os")
.expectOneCall("perror")
.withParameter("string", "i2cSetAddress");

ic2_entry_function(); //you did not show the entry point, so I made
this up
}

Then make the associated stub implementations surrounded by extern "C" {
}

//in a C++ file
extern "C"
{
int open(const char * path, int mode)
{
mock("os).actualCall("open")
.withParameter("path", path)
.withParameter("mode", mode)
.andReturnsIntValue();
}
}

Is that the kind of help you are looking for? Just work your way down
the functions, one case at a time.

exit() presents a problem, as it does not return.

James

--------------------------------------------------------------------------------------------
James Grenning Author of TDD for Embedded C
www.wingman-sw.com http://pragprog.com/titles/jgade/
www.wingman-sw.com/blog
www.twitter.com/jwgrenning
> --
> You received this message because you are subscribed to the Google
> Groups "cpputest" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to cpputest+u...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

Bill Heughan

unread,
Jul 1, 2014, 12:39:23 AM7/1/14
to cppu...@googlegroups.com
Hello James,

Thanks for your response, it has already cleared a few questions I had about just getting started. But I do have a few more if that's alright :)

So these mocks will replace the headers I have to include that contain the linux file function definitions, when I run my unit tests?

ie. stdio.h in the case of open()

Basically, I'm a little unclear on how to apply things to my case. I see some of your github code for your book includes the header files containing functions which are then mocked, inside your extern "C" definitions (the file is in the t1 folder of the tddec-code repository, the IO example). This IO example seems most analogous to my intended use, but I could be wrong. So I'm assuming all my necessary includes for the mocked functions go into my extern 'C' declaration.

Now, as for the entry point, if I intend to package this as a library, used as a dependency for other drivers which will also be libraries, what would you surmise my entry point could be in this case? A 'main' testing function? Or would this entry point lead toward more tests?

Generally the library will have:

I2C_open()
I2C_start()
I2C_write()
I2C_read()
I2C_close()

...with associated list of tests, of course. So as far as I surmise here, my entry point for my 'open' tests would be to the 'start' tests. Does that make sense?

It looks like I'm stuck on the starting line here, but I guess that's how you learn :)




To unsubscribe from this group and stop receiving emails from it, send an email to cpputest+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to a topic in the Google Groups "cpputest" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/cpputest/b1Dy6IRTrJo/unsubscribe.
To unsubscribe from this group and all its topics, send an email to cpputest+unsubscribe@googlegroups.com.

billh...@ecologix.ca

unread,
Jul 1, 2014, 3:18:10 AM7/1/14
to cppu...@googlegroups.com
Okay, so I've gotten a little further. I think I have the beginnings of what I want, but I'm running into a double declaration error. My mocked 'open()' seems to be conflicting with the actual 'open()' in <sys/stat.h>:

bheughan@debian-elws1:~/github/bbbk-i2c-lib$ make
compiling i2ctest.cpp
tests/i2c-mock/i2ctest.cpp: In function 'int open(const char*, int)':
tests/i2c-mock/i2ctest.cpp:10:41: error: declaration of C function 'int open(const char*, int)' conflicts with
compilation terminated due to -Wfatal-errors.
make: *** [obj/tests/i2c-mock/i2ctest.o] Error 1

The place I actually include the include necessary for open() to be defined in my production code is in this file, line 1:


The header file for that source is here:


And my test, where I did not include the linux header in question for this reason:


I'm pretty sure I know why the error is happening (C standard doesn't allow multiple function definitions, obviously), but I'm unsure what would be the best thing here to fix it. Preprocessor perhaps? I had preferred to use the linker, however.

Anyway, thanks in advance for any help anyone has!


James Grenning

unread,
Jul 1, 2014, 8:02:00 AM7/1/14
to cppu...@googlegroups.com
The mocks you need in this case do not replace the headers, they replace
the implementations. (You can replace headers, but generally prefer not
to.) You use the linker to choose your version and proven the real
implementation from being linked.

The IO is analogous to what you are doing, except that the things you
want to mock come from a 3rd party <stdio.h>. I believe that stdio.h is
C++ aware and may do some conditional compilation based on the
__cplusplus symbol. This can cause some problems. Try and see.

The extern "C" is telling the linker to use C linkage rather than C++
linkage for the symbols in the extern "C" block. Each language has its
own conventions.

As far as getting started is concerned, the best thing to do is start
:-). See what error happens and try to change it. Go little by little
so that there is only one error at a time.

--------------------------------------------------------------------------------------------
James Grenning Author of TDD for Embedded C
www.wingman-sw.com http://pragprog.com/titles/jgade/
www.wingman-sw.com/blog
www.twitter.com/jwgrenning

>>> email to cpputest+u...@googlegroups.com.
>>>
>>> For more options, visit https://groups.google.com/d/optout.
>>>
>>
>> --
>> You received this message because you are subscribed to a topic in
>> the
>> Google Groups "cpputest" group.
>> To unsubscribe from this topic, visit https://groups.google.com/d/
>> topic/cpputest/b1Dy6IRTrJo/unsubscribe.
>> To unsubscribe from this group and all its topics, send an email to
>> cpputest+u...@googlegroups.com.
>> For more options, visit https://groups.google.com/d/optout.
>>
>
> --
> You received this message because you are subscribed to the Google
> Groups "cpputest" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to cpputest+u...@googlegroups.com.

James Grenning

unread,
Jul 1, 2014, 8:05:58 AM7/1/14
to cppu...@googlegroups.com
open() may be in the same compilation unit (C file) as some of your
other dependencies.

Linkers use a object file from a library to satisfy an undefined
external references. So even if you stub open, and your code also calls
read() and read is not stubbed, you will get the whole object file and a
multiple definition error.

Make stubs for open, close, read, write, ioctrl and your multiply
defined error may go away.
> --
> You received this message because you are subscribed to the Google
> Groups "cpputest" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to cpputest+u...@googlegroups.com.

billh...@ecologix.ca

unread,
Jul 1, 2014, 11:38:21 PM7/1/14
to cppu...@googlegroups.com
What I did instead was create my own inline container function for the open() function and mocked that...not sure if this is the best solution but it seems to work for me.

Anyway, thanks for your help!

Martin Ertsås

unread,
Jul 2, 2014, 2:51:04 AM7/2/14
to cppu...@googlegroups.com
Good that it works. For a more general solution you could have a look at fopencookie and it's friends. They are a bit more work to implement, but it's a nice api which lets you have full control of all file IO both in C and C++. We use it extensively in our code base.

- Martin
Reply all
Reply to author
Forward
0 new messages