-
Notifications
You must be signed in to change notification settings - Fork 53
/
TUTORIAL
1975 lines (1485 loc) · 48.3 KB
/
TUTORIAL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# -*- rd -*-
= Tutorial --- How to use Cutter
Copyright (C) 2007-2010 Kouhei Sutou <[email protected]>
: License
Triple license:
((<LGPLv3+|URL:URL:http://www.gnu.org/licenses/lgpl.html>)),
((<GFDLv1.3+|URL:http://www.gnu.org/licenses/fdl.html>)) and/or
((<CC-BY-SA|URL:http://creativecommons.org/licenses/by-sa/3.0/>))
.
== Introduction
We write a program (library) that implements a stack in C.
We write a program with writing tests. To write tests, we
use Cutter that is a unit testing framework for C.
We use GNU build system (GNU Autoconf/GNU Automake/GNU
Libtool) for build system. GNU build system lessens
disparities on build environment. For this reason, we can
build our program and tests on several environment easily.
It's better that a program works on several environment
without many costs. If tests of the program works on the
environment too, we can verify the program works well on the
environment easily. It's important that both a program and
tests are works well on several environment easily.
Cutter requires only GLib. GLib is a very portable library
that works on not only UNIX-like system but also Windows and
Mac OS X. Cutter provides many useful test support features
with portability due to GLib. Cutter is a testing framework
and respects to xUnit style.
We will learn how to use Cutter with writing a stack
implementation. We assume that Cutter is already installed
into your system.
There are source codes of this program in sample/stack/.
== Directory hierarchy
First, we need to setup a directory for our stack
program. We use 'stack' as the directory name.
% mkdir -p /tmp/stack
% cd /tmp/stack
Next, we make some directories: config/ that is for build
auxiliary files, src/ that is for our source files and test/
that is for tests.
[stack]% mkdir config src test
After the above, we get the following directory hierarchy:
stack/ -+- config/ for build auxiliary files
|
+- src/ for source files
|
+- test/ for tests
== Use GNU build system
In GNU build system start-up, some commands are ran and they
generates some files automatically. They usually are run
from an authgen.sh shell script. We follow the convention.
autogen.sh:
#!/bin/sh
run()
{
$@
if test $? -ne 0; then
echo "Failed $@"
exit 1
fi
}
run aclocal ${ACLOCAL_ARGS}
run libtoolize --copy --force
run autoheader
run automake --add-missing --foreign --copy
run autoconf
Don't forget to make the autogen.sh executable.
[stack]% chmod +x autogen.sh
run() is a convenience function to confirm a result of ran
command. The following list shows what is done by them:
* aclocal: collects macros that is used by Automake into aclocal.m4.
* libtoolize: prepares files that is needed by libtool.
* autoheader: generates config.h.in that is used by
configure script.
* automake: generates Makefile.in that is used by
configure script.
* autoconf: generates configure scripts.
If we installed Cutter into different prefix with aclocal's
install prefix, you need to set ACLOCAL_ARGS environment
variable. The environment variable is referred from
autogen.sh. If we installed Cutter with $HOME/local prefix,
here is an example command to set the environment variable:
[stack]% export ACLOCAL_ARGS="-I $HOME/local/share/aclocal"
The following is a result of autogen.sh at this point:
[stack]% ./autogen.sh
aclocal: `configure.ac' or `configure.in' is required
Failed aclocal
We need to prepare configure.ac that is for Autoconf.
=== configure.ac
The following is a minimum configure.ac for our autogen.sh.
configure.ac:
AC_PREREQ(2.59)
AC_INIT(stack, 0.0.1, [email protected])
AC_CONFIG_AUX_DIR([config])
AC_CONFIG_HEADER([src/config.h])
AM_INIT_AUTOMAKE($PACKAGE_NAME, $PACKAGE_VERSION)
AC_PROG_LIBTOOL
AC_CONFIG_FILES([Makefile])
AC_OUTPUT
The following is a result of autogen after preparing
configure.ac.
[stack]% ./autogen.sh
Putting files in AC_CONFIG_AUX_DIR, `config'.
configure.ac:7: installing `config/install-sh'
configure.ac:7: installing `config/missing'
automake: no `Makefile.am' found for any configure output
Failed automake --add-missing --foreign --copy
We need to prepare Makefile.am for Automake.
=== Makefile.am
An empty Makefile.am is enough if the Makefile.am is just
only for autogen.sh.
[stack]% touch Makefile.am
[stack]% ./autogen.sh
Putting files in AC_CONFIG_AUX_DIR, `config'.
A configure script can be generated. We can do 'configure;
make; make install' like many popular softwares at this
point:
[stack]% ./configure
...
[stack]% make
[stack]% make install
But for now, nothing is to happen because we doesn't have
any items that are needed to build or install.
== First test writing
We can write a test because we got a minimal build
environment. First, we test that a newly created statck
should be empty. The following code representes this test in
C:
void
test_new_stack (void)
{
Stack *stack;
stack = stack_new();
if (stack_is_empty(stack))
PASS;
else
FAIL;
}
We change this test code to be able to run as a test code
for Cutter.
=== Write a test program
A test program is put into test/. In this tutorial, we make
a test program as test/test-stack.c.
First, we need to include cutter.h to use Cutter.
test/test-stack.c:
#include <cutter.h>
And we need to include stack.h that declares API for test target
stack implementation. (stack.h will be made later.)
test/test-stack.c:
#include <stack.h>
Next, we write a test with the stack API:
test/test-stack.c:
void test_new_stack (void);
void
test_new_stack (void)
{
Stack *stack;
stack = stack_new();
cut_assert(stack_is_empty(stack));
}
cut_assert() is a macro that fails if the first argument is
0, passes otherwise. Writing tests with Cutter means that
writing a program that verifies a target program works as
we expected at the specific situation.
The following test code is a whole test code to test "a
newly created stack should be empty".
test/test-stack.c:
#include <cutter.h>
#include <stack.h>
void test_new_stack (void);
void
test_new_stack (void)
{
Stack *stack;
stack = stack_new();
cut_assert(stack_is_empty(stack));
}
=== Build a test
Each test programs for Cutter are shared libraries. To build
the above test program as shared library, we change
Makefile.am.
==== Build configuration in test/
Makefile.am is empty for now.
First, put the following configuration to use ACLOCAL_ARGS
environment variable for autogen.sh with aclocal invoked
via make:
Makefile.am:
ACLOCAL_AMFLAGS = $$ACLOCAL_ARGS
Next, to build test/test-stack.c in test/ directory, we need
to specify that there is test/ directory as sub directory in
Makefile.am.
Makefile.am:
...
SUBDIRS = test
make will detect Makefile.am is changed and update Makefile
and so on automatically after we change Makefile.am and run
make.
[stack]% make
cd . && /bin/sh /tmp/stack/config/missing --run automake-1.10 --foreign Makefile
cd . && /bin/sh ./config.status Makefile
config.status: creating Makefile
Making all in test
config.status: creating Makefile
Making all in test
make[1]: Entering directory `/tmp/stack/test'
make[1]: *** No rule to make target `all'. Stop.
make[1]: Leaving directory `/tmp/stack/test'
make: *** [all-recursive] Error 1
We find that make go down to test/ to build. But make is
failed in test/ because test/Makefile doesn't exist.
To build in test/, we will make test/Makefile.am and indicate
configure.ac to generate test/Makefile.
An empty test/Makefile.am is OK for just protecting make
failure in test/.
[stack]% touch test/Makefile.am
Next, we indicate configure.ac to generate
test/Makefile. Now, make will be done successfully.
configure.ac:
...
AC_CONFIG_FILES([Makefile
test/Makefile])
...
If we run make again, make re-runs configure and
test/Makefile is generated. Now make doesn't fail in test/.
[stack]% make
...
config.status: creating test/Makefile
config.status: creating src/config.h
config.status: src/config.h is unchanged
config.status: executing depfiles commands
Making all in test
make[1]: Entering directory `/tmp/stack/test'
make[1]: Nothing to be done for `all'.
make[1]: Leaving directory `/tmp/stack/test'
make[1]: Entering directory `/tmp/stack'
make[1]: Nothing to be done for `all-am'.
make[1]: Leaving directory `/tmp/stack'
==== Build test/test_stack.so
We will edit test/Makefile.am to build test/test-stack.c as
a shared library. A shared library for test should be named
as "test_" prefix. (It's OK if "lib" is prepended to "test_"
prefix.) We use "noinst_" because a test program isn't
needed to be installed.
test/Makefile.am:
noinst_LTLIBRARIES = test_stack.la
Shared libraries for test are loaded dynamically by cutter
that is a command included in Cutter to run test. Shared
libraries that are loaded dynamically should be builded
libtool with -module option. -rpath option is also required
by -module option. Because of them LDFLAGS becomes the
following. The reason why -avoid-version is specified is
that shared libraries for test aren't needed to have version
number. -no-undefined option tells libtool that it reports a
error when there is any undefined symbol. On some
environments, shared library isn't generated without
-no-undefined option. (e.g. a case that generating DLL on
Windows.)
test/Makefile.am:
...
LDFLAGS = -module -rpath $(libdir) -avoid-version -no-undefined
To build test/test_stack.la, test/test-stack.c is
used. (test_stack.so is generated into test/.libs/.) We need
to specify this.
test/Makefile.am:
...
test_stack_la_SOURCES = test-stack.c
Now, we can build test/test_stack.la.
[stack]% make
...
cd .. && /bin/sh /tmp/stack/config/missing --run automake-1.10 --foreign test/Makefile
test/Makefile.am: required file `config/depcomp' not found
test/Makefile.am: `automake --add-missing' can install `depcomp'
make[1]: *** [Makefile.in] Error 1
make[1]: Leaving directory `/tmp/stack/test'
make: *** [all-recursive] Error 1
To generate config/depcomp, we need to run automake with
--add-missing option. To do this, we can use
autogen.sh. Don't forget to re-run configure.
[stack]% ./autogen.sh
[stack]% ./configure
Now, we can build test/test_stack.la with make.
[stack]% make
...
test-stack.c:1:20: error: cutter.h: No such file or directory
test-stack.c:2:19: error: stack.h: No such file or directory
test-stack.c: In function 'test_new_stack':
test-stack.c:9: error: 'Stack' undeclared (first use in this function)
test-stack.c:9: error: (Each undeclared identifier is reported only once
test-stack.c:9: error: for each function it appears in.)
test-stack.c:9: error: 'stack' undeclared (first use in this function)
make[1]: *** [test-stack.lo] Error 1
make[1]: Leaving directory `/tmp/stack/test'
make: *** [all-recursive] Error 1
But there are the above errors because we don't setup to use
Cutter yet. And we can't include stack.h because we don't
have a stack implementation yet.
==== Use Cutter
We will support cutter.h including. Cutter provides a macro
file for aclocal. Because of this, we can use Cutter with
GNU build system.
First, we add a code to detect Cutter into configure.ac.
configure.ac:
...
AC_CHECK_CUTTER
AC_CONFIG_FILES([Makefile
test/Makefile])
...
We use detected Cutter information in test/Makefile.am:
test/Makefile.am:
...
INCLUDES = $(CUTTER_CFLAGS)
LIBS = $(CUTTER_LIBS)
...
The followings are the current whole configure.ac,
Makefile.am and test/Makefile.am:
configure.ac:
AC_PREREQ(2.59)
AC_INIT(stack, 0.0.1, [email protected])
AC_CONFIG_AUX_DIR([config])
AC_CONFIG_HEADER([src/config.h])
AM_INIT_AUTOMAKE($PACKAGE_NAME, $PACKAGE_VERSION)
AC_PROG_LIBTOOL
AC_CHECK_CUTTER
AC_CONFIG_FILES([Makefile
test/Makefile])
AC_OUTPUT
Makefile.am:
ACLOCAL_AMFLAGS = $$ACLOCAL_ARGS
SUBDIRS = test
test/Makefile.am:
noinst_LTLIBRARIES = test_stack.la
INCLUDES = $(CUTTER_CFLAGS)
LIBS = $(CUTTER_LIBS)
LDFLAGS = -module -rpath $(libdir) -avoid-version -no-undefined
test_stack_la_SOURCES = test-stack.c
AC_CHECK_CUTTER macro uses pkg-config which is a popular
package information management tool. If we installed Cutter
with different prefix of pkg-config, we need to set
PKG_CONFIG_PATH environment variable. The environment
variable is referred by pkg-config to find .pc file. If we
installed Cutter with $HOME/local prefix, here is an example
command to set the environment variable:
[stack]% export PKG_CONFIG_PATH=$HOME/local/lib/pkgconfig
We run make again and make runs configure automatically and
builds with Cutter configuration after the above changes.
[stack]% make
...
test-stack.c:2:19: error: stack.h: No such file or directory
test-stack.c: In function 'test_new_stack':
test-stack.c:9: error: 'Stack' undeclared (first use in this function)
test-stack.c:9: error: (Each undeclared identifier is reported only once
test-stack.c:9: error: for each function it appears in.)
test-stack.c:9: error: 'stack' undeclared (first use in this function)
make[1]: *** [test-stack.lo] Error 1
make[1]: Leaving directory `/tmp/stack/test'
make: *** [all-recursive] Error 1
An error that reports "cutter.h can't be included" is gone away.
==== Make stack API
We will fix an error that stack.h can't be included.
We put stack.h into src/stack.h because we make a stack
implementation in src/.
[stack]% touch src/stack.h
To include stack.h from test program, we configure include
path:
test/Makefile.am:
...
INCLUDES = $(CUTTER_CFLAGS) -I$(top_srcdir)/src
...
We will find that an error that stack.h can't be included is
gone away if we run make again.
[stack]% make
...
test-stack.c: In function 'test_new_stack':
test-stack.c:9: error: 'Stack' undeclared (first use in this function)
test-stack.c:9: error: (Each undeclared identifier is reported only once
test-stack.c:9: error: for each function it appears in.)
test-stack.c:9: error: 'stack' undeclared (first use in this function)
make[1]: *** [test-stack.lo] Error 1
make[1]: Leaving directory `/tmp/stack/test'
make: *** [all-recursive] Error 1
There is only an error that Stack type isn't declared.
==== Declare Stack type
To build our test program, we declare Stack type in src/stack.h.
src/stack.h:
#ifndef __STACK_H__
#define __STACK_H__
typedef struct _Stack Stack;
#endif
We get a warning because stack_new() isn't declared but we
can build a shared library.
[stack]% make
...
test-stack.c: In function 'test_new_stack':
test-stack.c:10: warning: assignment makes pointer from integer without a cast
...
[stack]% file test/.libs/test_stack.so
test/.libs/test_stack.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, not stripped
NOTE: We can't generate a shared library (DLL) on Cygwin
when we have unresolved symbols. We can go to the next step
on Cygwin without caring the command result.
==== Declare stack_new()/stack_is_empty()
To suppress a warning, we declare stack_new() and stack_is_empty().
src/stack.h:
...
Stack *stack_new (void);
int stack_is_empty (Stack *stack);
...
We can confirm that make don't report any warnings now.
[stack]% make
=== Run test
Now, we can run a test because we got a shared library.
[stack]% cutter test/
cutter: symbol lookup error: test/.libs/test_stack.so: undefined symbol: stack_new
Loading our test is failed due to undefined stack_new() but
we can confirm that our test is loaded.
NOTE: We get a "0 tests are ran and no failure" result
report on Cygwin because we can't generate a DLL on Cygwin
when we have unresolved symbols. We will implement stack and
resolve all symbols. We can generate a DLL and run test
after implementing stack. We can go to the next step on
Cygwin without caring the command result.
==== Automate running test
GNU build system use 'make check' to run test. We follow
the convention in our stack implementation.
First, we make a script test/run-test.sh that runs our
test. A path of cutter command is passed from environment
variable CUTTER.
test/run-test.sh:
#!/bin/sh
export BASE_DIR="`dirname $0`"
$CUTTER -s $BASE_DIR "$@" $BASE_DIR
Don't forget to make the test/run-test.sh executable.
[stack]% chmod +x test/run-test.sh
We need to specify that we use test/run-test.sh as a test
runner script to test/Makefile.am.
test/Makefile.am:
TESTS = run-test.sh
TESTS_ENVIRONMENT = CUTTER="$(CUTTER)"
...
We pass a path of cutter command via environment variable
CUTTER in TESTS_ENVIRONMENT. A path of cutter command is
detected by AC_CHECK_CUTTER in configure.ac.
We can confirm that 'make -s check' runs our test. -s option
is for silence mode. A test result can be confirmed more easier.
[stack]% make -s check
Making check in test
cutter: symbol lookup error: ./.libs/test_stack.so: undefined symbol: stack_new
FAIL: run-test.sh
================================
1 of 1 tests failed
Please report to [email protected]
================================
...
NOTE: As mentioned the above, we doesn't get an error on
Cygwin because we can't generate a DLL for now. We doesn't
need to care it. We can go to the next.
==== Make test/run-test.sh workable alone
In 'make -s check', there are outputs that isn't test result
like build logs. They hid test result that is interested by
us. So we want test/run-test.sh to work without invoking
from 'make -s check'.
test/run-test.sh needs to detect a path of cutter command
automatically if environment variable CUTTER isn't set. And
test/run-test.sh needs to run make to rebuild necessary
files if test/run-test.sh isn't invoked from 'make check'.
test/run-test.sh:
#!/bin/sh
export BASE_DIR="`dirname $0`"
top_dir="$BASE_DIR/.."
if test -z "$NO_MAKE"; then
make -C $top_dir > /dev/null || exit 1
fi
if test -z "$CUTTER"; then
CUTTER="`make -s -C $BASE_DIR echo-cutter`"
fi
$CUTTER -s $BASE_DIR "$@" $BASE_DIR
To support the test/run-test.sh, test/Makefile.am has some
works.
test/Makefile.am:
...
TESTS_ENVIRONMENT = NO_MAKE=yes CUTTER="$(CUTTER)"
...
echo-cutter:
@echo $(CUTTER)
The following is the whole of test/Makefile.am.
test/Makefile.am:
TESTS = run-test.sh
TESTS_ENVIRONMENT = NO_MAKE=yes CUTTER="$(CUTTER)"
noinst_LTLIBRARIES = test_stack.la
INCLUDES = $(CUTTER_CFLAGS) -I$(top_srcdir)/src
LIBS = $(CUTTER_LIBS)
LDFLAGS = -module -rpath $(libdir) -avoid-version -no-undefined
test_stack_la_SOURCES = test-stack.c
echo-cutter:
@echo $(CUTTER)
We can confirm that test/run-test.sh runs test even if it's not
invoked from 'make -s check'.
[stack]% test/run-test.sh
cutter: symbol lookup error: test/.libs/test_stack.so: undefined symbol: stack_new
NOTE: We doesn't get the error on Cygwin.
We will use test/run-test.sh instead of 'make -s check' from
now. Test result that is what we are interested
in will not be hid because test/run-test.sh just outputs
build errors and/or warnings and test result.
We spent some times to build testing environment before we
implement stack. It reduces costs to run test. If costs to
run test isn't low, we will not run test gradually. It
may cause quality loss.
Building testing environment at first delays start time of
implementing a main program. But we need to keep quality of
a main program by running test until a main program is
developed and maintained. We will be able to collect costs
that is spent for building testing environment. It's
important that building testing environment at first to
be developing a high-quality program comfortably.
=== Implement stack
We will start implementing stack because we built testing
environment.
==== A straightforward stack_new() implementation
We will define stack_new() and resolve run-time error.
We implement stack in src/stack.c. It's a straightforward
stack_new() implementation:
src/stack.c:
#include <stdlib.h>
#include "stack.h"
Stack *
stack_new (void)
{
return NULL;
}
==== Build src/libstack.la
We will build src/stack.c with make. src/ should be included
into build targets like test/.
Makefile.am:
ACLOCAL_AMFLAGS = $$ACLOCAL_ARGS
SUBDIRS = src test
configure.ac:
...
AC_CONFIG_FILES([Makefile
src/Makefile
test/Makefile])
...
The above configurations are for what we want to do.
[stack]% test/run-test.sh
configure.ac:19: required file `src/Makefile.in' not found
make: *** [Makefile.in] Error 1
To resolve the above error, we need to make src/Makefile.am.
[stack]% touch src/Makefile.am
[stack]% test/run-test.sh
cutter: symbol lookup error: test/.libs/test_stack.so: undefined symbol: stack_new
NOTE: We doesn't get the error on Cygwin.
make doesn't report error but we still have an error that
stack_new() is undefined. Because we don't build src/stack.c
and test program also doesn't link libstack.so yet.
The following configurations in src/Makefile.am are for
build libstack.so from src/stack.c.
src/Makefile.am:
lib_LTLIBRARIES = libstack.la
LDFLAGS = -no-undefined
libstack_la_SOURCES = stack.c
make will generate libstack.so.
[stack]% make
...
make[1]: Entering directory `/tmp/stack/src'
Makefile:275: .deps/stack.Plo: No such file or directory
make[1]: *** No rule to make target `.deps/stack.Plo'. Stop.
...
To resolve the above error, we need to re-run configure.
[stack]% ./configure
make will generate src/.libs/libstack.so.0.0.0 now.
[stack]% make
[stack]% file src/.libs/libstack.so.0.0.0
src/.libs/libstack.so.0.0.0: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, not stripped
NOTE: We will generate src/.libs/cyglibstack.dll on Cygwin.
==== Link src/libstack.la
libstack.so is generated but it's not linked into test
program. So there is still run-time error.
[stack]% test/run-test.sh
cutter: symbol lookup error: test/.libs/test_stack.so: undefined symbol: stack_new
NOTE: We doesn't get the error on Cygwin.
To link libstack.so, we will change test/Makefile.am like
the following.
test/Makefile.am:
...
LIBS = $(CUTTER_LIBS) $(top_builddir)/src/libstack.la
...
We need to add src/.libs to library search paths in the environment,
before running cutter, so that it will find our code libraries.
The following makes the settings neeeded for
Cygwin, Darwin (including macOS), and BSD Unix:
test/run-test.sh:
...
case `uname` in
CYGWIN*)
PATH="$top_dir/src/.libs:$PATH"
;;
Darwin)
DYLD_LIBRARY_PATH="$top_dir/src/.libs:$DYLD_LIBRARY_PATH"
export DYLD_LIBRARY_PATH
;;
*BSD)
LD_LIBRARY_PATH="$top_dir/src/.libs:$LD_LIBRARY_PATH"
export LD_LIBRARY_PATH
;;
*)
:
;;
esac
$CUTTER -s $BASE_DIR "$@" $BASE_DIR
We need to run 'make clean' to re-link our test program.
[stack]% make clean
[stack]% make
[stack]% test/run-test.sh
cutter: symbol lookup error: test/.libs/test_stack.so: undefined symbol: stack_is_empty
An error message is changed to stack_is_empty() isn't found
from stack_new() isn't found. We can confirm that
libstack.so is linked correctly by this change.
NOTE: We doesn't get the error on Cygwin.
==== Implement stack_is_empty()
We test a result of stack_is_empty() in our test program:
test/test-stack.c:
...
cut_assert(stack_is_empty(stack));
...
That means that stack_is_empty() should return true. So
stack_is_empty() implementation in src/stack.c should return
true.
src/stack.c:
...
#define TRUE 1
#define FALSE 0
...
int
stack_is_empty (Stack *stack)
{
return TRUE;
}
The following is the whole of src/stack.c.
src/stack.c:
#include <stdlib.h>
#include "stack.h"
#define TRUE 1
#define FALSE 0
Stack *
stack_new (void)
{
return NULL;
}
int
stack_is_empty (Stack *stack)
{
return TRUE;
}
Our test should pass because the stack_is_empty()
implementation always returns true.
[stack]% test/run-test.sh
.
Finished in 0.000028 seconds
1 test(s), 1 assertion(s), 0 failure(s), 0 error(s), 0 pending(s), 0 omission(s), 0 notification(s)
100% passed
Great! This is the first success!!!
Displayed a "." means that a test is passed. The current
number of tests is just one. So one "." means all tests are
passed.
The above result may be displayed in green. This means that
we may go to the next step because our all tests are passed.
We confirmed that test is worked. We will complete stack
implementation with writing tests.
== Implement push
We will implement push. We only accept integer for values in
stack in this implementation.
=== Test for push
A stack should have 1 item and not be empty after we push a
value. The following is a test for this.
test/test-stack.c:
...
void test_push (void);
...
void
test_push (void)
{
Stack *stack;
stack = stack_new();
cut_assert_equal_int(0, stack_get_size(stack));
stack_push(stack, 100);
cut_assert_equal_int(1, stack_get_size(stack));
cut_assert(!stack_is_empty(stack));
}
We will get an error that says stack_get_size() isn't
undefined if we run test.
[stack]% test/run-test.sh
cutter: symbol lookup error: ./test/.libs/test_stack.so: undefined symbol: stack_get_size
We will implement push to pass this test.
NOTE: We doesn't get the error on Cygwin.
=== Implement cut_stack_push()
We will implement stack_get_size() and stack_push() to be
able to run test even if tests aren't passed.
First, we add declarations to src/stack.h.
src/stack.h:
...
int stack_get_size (Stack *stack);
void stack_push (Stack *stack, int value);
...
And we add definitions to src/stack.c.
src/stack.c:
...
int
stack_get_size (Stack *stack)
{
return 0;
}
void
stack_push (Stack *stack, int value)
{
}
The reason why stack_get_size() returns 0 is the first
stack_get_size() call is expected to return 0 like the following.
test/test-stack.c:
...
stack = stack_new();
cut_assert_equal_int(0, stack_get_size(stack));
...
We run test because push is implemented.
[stack]% test/run-test.sh
.F
1) Failure: test_push
<1 == stack_get_size(stack)>
expected: <1>
but was: <0>
test/test-stack.c:23: test_push()