Mark Probert said:
I have just started using the excellent Rake tool (thanks, Jim!) and I am
at a
bit of a loss on how to proceed. I am attempting to create unit test for
some C++ code I am creating, using the cxxtest tool.
Hi Mark, Sorry it took me so long to respond to this. You probably have
solved this already, but I'll post for the general education of all Rake
Users everywhere (well, at least the ones on this list.
cxxtest has its tests contained in a .h file. These are then parsed by
the tool to give your .cpp file. This is then complied and linked
with the object file.
So, assuming I have my code in foo.cpp, my tests in foo_test.h, then the
sequence looks like:
$ c++ -c -o foo.o foo.cpp
$ cxxtest.pl -o foo_test.cpp foo_test.h
$ c++ -o foo_test foo_test.cpp foo.o
Ok, I'm going to work with this example rather than the Rakefile you
posted below. There should be enough info to apply it to your rakefile.
So, there are two issues that I am having problems with. The first is
turning the .h into a .cpp.
Not a problem ... You want to generate .cpp files from .h files with the
same base name. The easiest way is to express this as an explicit file
task:
file "foo_test.cpp" => ["foo_test.h"] do
sh %{cxxtest.pl -o foo_test.cpp foo_test.h}
end
The above says that file "foo_test.cpp" depends on a header file named
"foo_test.h", and that to create the .cpp file, all you need to do is run
the cxxtest.pl command with the given parameters.
Using the same logic, we can come up with tasks for the other two files as
well:
file "foo.o" => ["foo.cpp"] do
sh %{c++ -c -o foo.o foo.cpp}
end
file "foo_test" => ["foo_test.cpp", "foo.o"] do
sh %{c++ -o foo_test foo_test.cpp foo.o}
end
Those three file tasks together in a rake file will build foo_test
whenever you type "rake foo_test" at the command line. Supposedly you
want to run the tests as well. Simply add
task :unittest => ["foo_test"] do
sh "foo_test"
end
And if you want that to be the default task, then add:
task :default => [:unittest]
Take all of the above together and you get a working Rakefile. Now lets
fine tune it a bit.
First of all, the Rakefile is overly restrictive. Suppose you added
"bar_test.h" to the mix? You would have to add a task to build it and
modify existing tasks to depend upon it. Fortunately, you are using Ruby,
and you can create those tasks in a test...
FileList['*_test.h'].each do |source|
target = source.sub(/\.h$/, '.cpp')
file target => [fn] do
sh "cxxtest.pl -o #{target} #{source}"
end
end
We are assuming that the test header files contain end in "_test.h".
Modify according to your needs.
If you have 20 test headers, the above code will create 20 explicit tasks
for generating the test cpp files. This works great, however, there is
another way ... rules.
rule '.cpp' => '.h' do |t|
sh "cxxtest.pl -o #{t.name} #{t.source}"
end
This rule says that whenever you need a .cpp file, and you have a .h file
with the same name, here's how you would generate the .cpp file. It is
similar to generating the tasks explicitly (as we did in the loop above),
but instead of generating the tasks all at once it only does it on an as
needed basis.
In the same way, we can give a rule for compiling .cpp files into .o files.
rule '.o' => '.cpp' do |t|
sh "c++ -c -o #{t.name} #{t.source}"
end
I also like to create a bunch of file lists that describe the categories
of files. Here's what might work for you ...
CPP_SOURCE = FileList['*.cpp'].exclude('*_test.cpp')
TEST_HEADERS = FileList['*_test.h']
Now I want to combine these lists into a single list of all the object
files I will be dealing with ...
OBJ = FileList[
CPP_SOURCE.sub(/\.cpp$/, '.o'),
TEST_HEADERS.sub(/\.h$/, '.o'),
]
That leaves our final compile rule to look like this ...
file "foo_test" => OBJ do
sh "c++ -o foo_test #{OBJ}"
end
I'll show the complete Rakefile at the end and include some simple
clean/clobber rules.
The second is how to get the test to conditionally depend on foo.o. I
only want to create foo.o if it isn't there. [...]
Not a problem. Rake will only regenerate a file in a file task when the
file is out of date w.r.t. its dependencies.
Anyway, here is my rakefile, which isn't quite right. Giving:
$ rake
c++ -o unittest unittest scanner.o
c++: unittest: No such file or directory
rake aborted!
In your :unittest task, you are compiling and EXE, but give it t.name as
the file to compile. Since the task name is :unittest, there is no file
by that name.
Here's my Rakefile ...
···
----------------------------------------------------
# -*- ruby -*-
require 'rake/clean'
CLEAN.include('*.o')
CLOBBER.include('foo_test', 'foo_test.cpp')
CPP_SOURCE = FileList['*.cpp'].exclude(/test.cpp$/)
TEST_HEADERS = FileList['*_test.h']
OBJ = FileList[
CPP_SOURCE.sub(/\.cpp$/, '.o'),
TEST_HEADERS.sub(/\.h$/, '.o'),
]
task :default => :unittest
task :unittest => "foo_test" do
sh "foo_test"
end
file "foo_test" => OBJ do
sh "c++ -o foo_test #{OBJ}"
end
rule '.o' => '.cpp' do |t|
sh "c++ -c -o #{t.name} #{t.source}"
end
rule '.cpp' => '.h' do |t|
sh "cxxtest.pl -o #{t.name} #{t.source}"
end
----------------------------------------------------
--
-- Jim Weirich jim@weirichhouse.org http://onestepback.org
-----------------------------------------------------------------
"Beware of bugs in the above code; I have only proved it correct,
not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)